// // x65.cpp // // // Created by Carl-Henrik Skårstedt on 9/23/15. // // // A "simple" 6502/65C02/65816 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 <vector> #include <stdio.h> #include <stdlib.h> #include <inttypes.h> #include <assert.h> // Command line arguments static const strref cmdarg_listing("lst"); // -lst / -lst=(file.lst) : generate disassembly text from result(file or stdout) static const strref cmdarg_tass_listing("tsl"); // -tsl=(file) : generate listing file in TASS style static const strref cmdarg_tass_labels("tl"); // -tl=(file) : generate labels in TASS style static const strref cmdarg_allinstr("opcodes"); // -opcodes / -opcodes=(file.s) : dump all available opcodes(file or stdout) static const strref cmdarg_endmacro("endm"); // -endm : macros end with endm or endmacro instead of scoped('{' - '}') static const strref cmdarg_cpu("cpu"); // declare CPU type, use with argument: -cpu=6502/65c02/65c02wdc/65816 static const strref cmdarg_acc("acc"); // [65816] -acc=8/16: set the accumulator mode for 65816 at start, default is 8 bits static const strref cmdarg_xy("xy"); // [65816] -xy=8/16: set the index register mode for 65816 at start, default is 8 bits static const strref cmdarg_org("org"); // -org = $2000 or - org = 4096: force fixed address code at address static const strref cmdarg_kickasm("kickasm"); // -kickasm: use Kick Assembler syntax static const strref cmdarg_merlin("merlin"); // -merlin: use Merlin syntax static const strref cmdarg_c64("c64"); // -c64 : Include load address(default) static const strref cmdarg_a2b("a2b"); // -a2b : Apple II Dos 3.3 Binary static const strref cmdarg_bin("bin"); // -bin : Produce raw binary\n" static const strref cmdarg_a2p("a2p"); // -a2p : Apple II ProDos Binary static const strref cmdarg_a2o("a2o"); // -a2o : Apple II GS OS executable (relocatable) static const strref cmdarg_mrg("mrg"); // -mrg : Force merge all sections (use with -a2o) static const strref cmdarg_sect("sect"); // -sect: display sections loaded and built static const strref cmdarg_sym("sym"); // -sym (file.sym) : generate symbol file static const strref cmdarg_obj("obj"); // -obj (file.x65) : generate object file for later linking static const strref cmdarg_vice("vice"); // -vice (file.vs) : export a vice symbol file static const strref cmdarg_xrefimp("xrefimp"); // -xrefimp : import directive means xref, not include/incbin // 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_BYTES 255 // Max number of exported binary files from a single source #define MAX_EXPORT_FILES 64 // Maximum number of opcodes, aliases and directives #define MAX_OPCODES_DIRECTIVES 320 // minor variation of 6502 #define NUM_ILLEGAL_6502_OPS 21 // minor variation of 65C02 #define NUM_WDC_65C02_SPECIFIC_OPS 18 // To simplify some syntax disambiguation the preferred // ruleset can be specified on the command line. enum AsmSyntax { SYNTAX_SANE, SYNTAX_KICKASM, 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_XREF_DEPENDENT, // evaluated but relied on an XREF label to do so STATUS_NOT_STRUCT, // return is not a struct. STATUS_EXPORT_NO_CODE_OR_DATA_SECTION, 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_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_COULD_NOT_INCLUDE_FILE, ERROR_PULL_WITHOUT_PUSH, ERROR_USER, ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution ERROR_BRANCH_OUT_OF_RANGE, ERROR_INCOMPLETE_FUNCTION, ERROR_FUNCTION_DID_NOT_RESOLVE, ERROR_EXPRESSION_RECURSION, 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_MACRO_ARGUMENT, 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, ERROR_CPU_NOT_SUPPORTED, ERROR_CANT_APPEND_SECTION_TO_TARGET, ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE, ERROR_NOT_A_SECTION, ERROR_CANT_REASSIGN_FIXED_SECTION, ERROR_CANT_LINK_ZP_AND_NON_ZP, ERROR_OUT_OF_MEMORY, ERROR_CANT_WRITE_TO_FILE, ERROR_ABORTED, ERROR_CONDITION_TOO_NESTED, STATUSCODE_COUNT }; // The following strings are in the same order as StatusCode const char *aStatusStrings[STATUSCODE_COUNT] = { "ok", // STATUS_OK, // everything is fine "relative section", // STATUS_RELATIVE_SECTION, // value is relative to a single section "not ready", // STATUS_NOT_READY, // label could not be evaluated at this time "XREF dependent result", // STATUS_XREF_DEPENDENT, // evaluated but relied on an XREF label to do so "name is not a struct", // STATUS_NOT_STRUCT, // return is not a struct. "Exporting binary without code or data section", // STATUS_EXPORT_NO_CODE_OR_DATA_SECTION, "Undefined code", // ERROR_UNDEFINED_CODE = FIRST_ERROR, "Unexpected character in expression", // ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION, "Too many values in expression", // ERROR_TOO_MANY_VALUES_IN_EXPRESSION, "Too many operators in expression", // ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION, "Unbalanced right parenthesis in expression", // ERROR_UNBALANCED_RIGHT_PARENTHESIS, "Expression operation", // ERROR_EXPRESSION_OPERATION, "Expression missing values", // ERROR_EXPRESSION_MISSING_VALUES, "Instruction can not be zero page", // ERROR_INSTRUCTION_NOT_ZP, "Invalid addressing mode for instruction", // ERROR_INVALID_ADDRESSING_MODE, "Internal label organization mishap", // ERROR_LABEL_MISPLACED_INTERNAL, "Bad addressing mode", // ERROR_BAD_ADDRESSING_MODE, "Unexpected character in addressing mode", // ERROR_UNEXPECTED_CHARACTER_IN_ADDRESSING_MODE, "Unexpected label assignment format", // ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT, "Changing value of label that is constant", // ERROR_MODIFYING_CONST_LABEL, "Out of labels in pool", // ERROR_OUT_OF_LABELS_IN_POOL, "Internal label pool release confusion", // ERROR_INTERNAL_LABEL_POOL_ERROR, "Label pool range evaluation failed", // ERROR_POOL_RANGE_EXPRESSION_EVAL, "Label pool was redeclared within its scope", // ERROR_LABEL_POOL_REDECLARATION, "Pool label already defined", // ERROR_POOL_LABEL_ALREADY_DEFINED, "Struct already defined", // ERROR_STRUCT_ALREADY_DEFINED, "Referenced struct not found", // ERROR_REFERENCED_STRUCT_NOT_FOUND, "Declare constant type not recognized (dc.?)", // ERROR_BAD_TYPE_FOR_DECLARE_CONSTANT, "rept count expression could not be evaluated", // ERROR_REPT_COUNT_EXPRESSION, "hex must be followed by an even number of hex numbers", // ERROR_HEX_WITH_ODD_NIBBLE_COUNT, "DS directive failed to evaluate immediately", // ERROR_DS_MUST_EVALUATE_IMMEDIATELY, "File is not a valid x65 object file", // ERROR_NOT_AN_X65_OBJECT_FILE, "Failed to read include file", // ERROR_COULD_NOT_INCLUDE_FILE, "Using symbol PULL without first using a PUSH", // ERROR_PULL_WITHOUT_PUSH "User invoked error", // ERROR_USER, "Errors after this point will stop execution", // ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution "Branch is out of range", // ERROR_BRANCH_OUT_OF_RANGE, "Function declaration is missing name or expression", // ERROR_INCOMPLETE_FUNCTION, "Function could not resolve the expression", // ERROR_FUNCTION_DID_NOT_RESOLVE "Expression evaluateion recursion too deep", // ERROR_EXPRESSION_RECURSION "Target address must evaluate immediately for this operation", // ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY, "Scoping is too deep", // ERROR_TOO_DEEP_SCOPE, "Unbalanced scope closure", // ERROR_UNBALANCED_SCOPE_CLOSURE, "Unexpected macro formatting", // ERROR_BAD_MACRO_FORMAT, "Align must evaluate immediately", // ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY, "Out of memory for macro expansion", // ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION, "Problem with macro argument", // ERROR_MACRO_ARGUMENT, "Conditional could not be resolved", // ERROR_CONDITION_COULD_NOT_BE_RESOLVED, "#endif encountered outside conditional block", // ERROR_ENDIF_WITHOUT_CONDITION, "#else or #elif outside conditional block", // ERROR_ELSE_WITHOUT_IF, "Struct can not be assembled as is", // ERROR_STRUCT_CANT_BE_ASSEMBLED, "Enum can not be assembled as is", // ERROR_ENUM_CANT_BE_ASSEMBLED, "Conditional assembly (#if/#ifdef) was not terminated in file or macro", // ERROR_UNTERMINATED_CONDITION, "rept is missing a scope ('{ ... }')", // ERROR_REPT_MISSING_SCOPE, "Link can only be used in a fixed address section", // ERROR_LINKER_MUST_BE_IN_FIXED_ADDRESS_SECTION, "Link can not be used in dummy sections", // ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION, "Can not process this line", // ERROR_UNABLE_TO_PROCESS, "Unexpected target offset for reloc or late evaluation", // ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE, "CPU is not supported", // ERROR_CPU_NOT_SUPPORTED, "Can't append sections", // ERROR_CANT_APPEND_SECTION_TO_TARGET, "Zero page / Direct page section out of range", // ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE, "Attempting to assign an address to a non-existent section", // ERROR_NOT_A_SECTION, "Attempting to assign an address to a fixed address section", // ERROR_CANT_REASSIGN_FIXED_SECTION, "Can not link a zero page section with a non-zp section", // ERROR_CANT_LINK_ZP_AND_NON_ZP, "Out of memory while building", // ERROR_OUT_OF_MEMORY, "Can not write to file", // ERROR_CANT_WRITE_TO_FILE, "Assembly aborted", // ERROR_ABORTED, "Condition too deeply nested", // ERROR_CONDITION_TOO_NESTED, }; // Assembler directives enum AssemblerDirective { AD_CPU, // CPU: Assemble for this target, AD_ORG, // ORG: Assemble as if loaded at this address AD_EXPORT, // EXPORT: export this section or disable export 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_MERGE, // MERGE: Merge named sections in order listed AD_LINK, // LINK: Put sections with this name at this address (must be ORG / fixed address section) AD_XDEF, // XDEF: Externally declare a symbol AD_XREF, // XREF: Reference an external symbol 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_IMPORT, // IMPORT: Include or Incbin or Incobj or Incsym AD_CONST, // CONST: Prevent a label from mutating during assemble AD_LABEL, // LABEL: Create a mutable label (optional) AD_STRING, // STRING: Declare a string symbol AD_FUNCTION, // FUNCTION: Declare a user defined function AD_UNDEF, // UNDEF: remove a string or a label 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_IFNDEF, // #IFNDEF: Conditional assembly inverted from IFDEF AD_IFCONST, // #IFCONST: Conditional assembly follows based on label being const AD_IFBLANK, // #IFBLANK: Conditional assembly follows based on rest of line empty AD_IFNBLANK, // #IFNBLANK: Conditional assembly follows based on rest of line not empty 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_A16, // A16: Set 16 bit accumulator mode AD_A8, // A8: Set 8 bit accumulator mode AD_XY16, // A16: Set 16 bit index register mode AD_XY8, // A8: Set 8 bit index register mode AD_HEX, // HEX: LISA assembler data block AD_ABORT, // ABORT: stop assembler and error 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_SCOPE, // SCOPE: Begin ca65 style scope AD_ENDSCOPE, // ENDSCOPR: End ca65 style scope AD_PUSH, // PUSH: Push the value of a variable symbol on a stack AD_PULL, // PULL: Pull the value of a variable symbol from its stack, must be pushed first 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. AD_SAV, // SAV: MERLIN version of export but contains full filename, not an appendable name AD_XC, // XC: MERLIN version of setting CPU AD_MX, // MX: MERLIN control accumulator 16 bit mode AD_LNK, // LNK: MERLIN load object and link AD_ADR, // ADR: MERLIN store 3 byte word AD_ADRL, // ADRL: MERLIN store 4 byte word AD_ENT, // ENT: MERLIN extern this address label AD_EXT, // EXT: MERLIN reference this address label from a different file AD_CYC, // CYC: MERLIN start / stop cycle timer AD_ERROR, }; // evaluation functions enum EvalFuncs { EF_DEFINED, // DEFINED(label) 1 if label is defined EF_REFERENCED, // REFERENCED(label) 1 if label has been referenced in this file EF_BLANK, // BLANK() 1 if the contents within the parenthesis is empty EF_CONST, // CONST(label) 1 if label is a const label EF_SIZEOF, // SIZEOF(struct) returns size of structs EF_SIN, // SIN(index, period, amplitude) }; // 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_EQU, // b, 1 if left equal to right otherwise 0 EVOP_LT, // c, 1 if left less than right otherwise 0 EVOP_GT, // d, 1 if left greater than right otherwise 0 EVOP_LTE, // e, 1 if left less than or equal to right otherwise 0 EVOP_GTE, // f, 1 if left greater than or equal to right otherwise 0 EVOP_LOB, // g, low byte of 16 bit value EVOP_HIB, // h, high byte of 16 bit value EVOP_BAB, // i, bank byte of 24 bit value EVOP_LPR, // j, left parenthesis EVOP_RPR, // k, right parenthesis EVOP_ADD, // l, + EVOP_SUB, // m, - EVOP_MUL, // n, * (note: if not preceded by value or right paren this is current PC) EVOP_DIV, // o, / EVOP_AND, // p, & EVOP_OR, // q, | EVOP_EOR, // r, ^ EVOP_SHL, // s, << EVOP_SHR, // t, >> EVOP_NOT, // u, ~ EVOP_NEG, // v, negate value EVOP_STP, // w, Unexpected input, should stop and evaluate what we have EVOP_NRY, // x, Not ready yet EVOP_XRF, // y, value from XREF label EVOP_EXP, // z, sub expression EVOP_ERR, // z+1, Error }; // Opcode encoding typedef struct sOPLookup { uint32_t op_hash; uint8_t index; // ground index uint8_t type; // mnemonic or } OPLookup; enum AddrMode { // address mode bit index // 6502 AMB_ZP_REL_X, // 0 ($12,x) AMB_ZP, // 1 $12 AMB_IMM, // 2 #$12 AMB_ABS, // 3 $1234 AMB_ZP_Y_REL, // 4 ($12),y AMB_ZP_X, // 5 $12,x AMB_ABS_Y, // 6 $1234,y AMB_ABS_X, // 7 $1234,x AMB_REL, // 8 ($1234) AMB_ACC, // 9 A AMB_NON, // a // 65C02 AMB_ZP_REL, // b ($12) AMB_REL_X, // c ($1234,x) AMB_ZP_ABS, // d $12, *+$12 // 65816 AMB_ZP_REL_L, // e [$02] AMB_ZP_REL_Y_L, // f [$00],y AMB_ABS_L, // 10 $bahilo AMB_ABS_L_X, // 11 $123456,x AMB_STK, // 12 $12,s AMB_STK_REL_Y, // 13 ($12,s),y AMB_REL_L, // 14 [$1234] AMB_BLK_MOV, // 15 $12,$34 AMB_COUNT, AMB_FLIPXY = AMB_COUNT, // 16 (indexing index using y treat as x address mode) AMB_BRANCH, // 17 (relative address 8 bit) AMB_BRANCH_L, // 18 (relative address 16 bit) AMB_IMM_DBL_A, // 19 (immediate mode can be doubled in 16 bit mode) AMB_IMM_DBL_XY, // 1a (immediate mode can be doubled in 16 bit mode) AMB_ILL, // 1b illegal address mode // address mode masks AMM_NON = 1<<AMB_NON, AMM_IMM = 1<<AMB_IMM, AMM_ABS = 1<<AMB_ABS, AMM_REL = 1<<AMB_REL, AMM_ACC = 1<<AMB_ACC, AMM_ZP = 1<<AMB_ZP, AMM_ABS_X = 1<<AMB_ABS_X, AMM_ABS_Y = 1<<AMB_ABS_Y, AMM_ZP_X = 1<<AMB_ZP_X, AMM_ZP_REL_X = 1<<AMB_ZP_REL_X, AMM_ZP_Y_REL = 1<<AMB_ZP_Y_REL, AMM_ZP_REL = 1<<AMB_ZP_REL, // b ($12) AMM_REL_X = 1<<AMB_REL_X, // c ($1234,x) AMM_ZP_ABS = 1<<AMB_ZP_ABS, // d $12, *+$12 AMM_ZP_REL_L = 1<<AMB_ZP_REL_L, // e [$02] AMM_ZP_REL_Y_L = 1<<AMB_ZP_REL_Y_L, // f [$00],y AMM_ABS_L = 1<<AMB_ABS_L, // 10 $bahilo AMM_ABS_L_X = 1<<AMB_ABS_L_X, // 11 $123456,x AMM_STK = 1<<AMB_STK, // 12 $12,s AMM_STK_REL_Y = 1<<AMB_STK_REL_Y, // 13 ($12,s),y AMM_REL_L = 1<<AMB_REL_L, // 14 [$1234] AMM_BLK_MOV = 1<<AMB_BLK_MOV, // 15 $12,$34 AMM_FLIPXY = 1<<AMB_FLIPXY, AMM_BRANCH = 1<<AMB_BRANCH, AMM_BRANCH_L = 1<<AMB_BRANCH_L, AMM_IMM_DBL_A = 1<<AMB_IMM_DBL_A, AMM_IMM_DBL_XY = 1<<AMB_IMM_DBL_XY, // instruction group specific masks AMM_BRK = AMM_NON | AMM_IMM, AMM_BRA = AMM_BRANCH | AMM_ABS, AMM_ORA = AMM_IMM | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_Y | AMM_ABS_X | AMM_ZP_REL_X | AMM_ZP_Y_REL, AMM_STA = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_Y | AMM_ABS_X | AMM_ZP_REL_X | AMM_ZP_Y_REL, AMM_ASL = AMM_ACC | AMM_NON | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X, AMM_STX = AMM_FLIPXY | AMM_ZP | AMM_ZP_X | AMM_ABS, // note: for x ,x/,y flipped for this instr. AMM_LDX = AMM_FLIPXY | AMM_IMM | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X, // note: for x ,x/,y flipped for this instr. AMM_STY = AMM_ZP | AMM_ZP_X | AMM_ABS, AMM_LDY = AMM_IMM | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X, AMM_DEC = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X, AMM_BIT = AMM_ZP | AMM_ABS, AMM_JMP = AMM_ABS | AMM_REL, AMM_CPY = AMM_IMM | AMM_ZP | AMM_ABS, // 6502 illegal modes AMM_SLO = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_Y | AMM_ABS_X | AMM_ZP_REL_X | AMM_ZP_Y_REL, AMM_SAX = AMM_FLIPXY | AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ABS, AMM_LAX = AMM_FLIPXY | AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ABS | AMM_ABS_X | AMM_ZP_Y_REL, AMM_AHX = AMM_FLIPXY | AMM_ZP_REL_X | AMM_ABS_X, AMM_SHY = AMM_ABS_X, AMM_SHX = AMM_ABS_Y, // 65C02 groups AMC_ORA = AMM_ORA | AMM_ZP_REL, AMC_STA = AMM_STA | AMM_ZP_REL, AMC_BIT = AMM_BIT | AMM_IMM | AMM_ZP_X | AMM_ABS_X, AMC_DEC = AMM_DEC | AMM_NON | AMM_ACC, AMC_JMP = AMM_JMP | AMM_REL_X, AMC_STZ = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X, AMC_TRB = AMM_ZP | AMM_ABS, AMC_BBR = AMM_ZP_ABS, // 65816 groups AM8_JSR = AMM_ABS | AMM_ABS_L | AMM_REL_X, AM8_JSL = AMM_ABS_L, AM8_BIT = AMM_IMM_DBL_A | AMC_BIT, AM8_ORA = AMM_IMM_DBL_A | AMC_ORA | AMM_STK | AMM_ZP_REL_L | AMM_ABS_L | AMM_STK_REL_Y | AMM_ZP_REL_Y_L | AMM_ABS_L_X, AM8_STA = AMC_STA | AMM_STK | AMM_ZP_REL_L | AMM_ABS_L | AMM_STK_REL_Y | AMM_ZP_REL_Y_L | AMM_ABS_L_X, AM8_ORL = AMM_ABS_L | AMM_ABS_L_X, AM8_STL = AMM_ABS_L | AMM_ABS_L_X, AM8_LDX = AMM_IMM_DBL_XY | AMM_LDX, AM8_LDY = AMM_IMM_DBL_XY | AMM_LDY, AM8_CPY = AMM_IMM_DBL_XY | AMM_CPY, AM8_JMP = AMC_JMP | AMM_REL_L | AMM_ABS_L | AMM_REL_X, AM8_JML = AMM_REL_L | AMM_ABS_L, AM8_BRL = AMM_BRANCH_L | AMM_ABS, AM8_MVN = AMM_BLK_MOV, AM8_PEI = AMM_ZP_REL, AM8_PER = AMM_BRANCH_L | AMM_ABS, AM8_REP = AMM_IMM | AMM_ZP, // Merlin allows this to look like a zp access }; struct mnem { const char *instr; uint32_t modes; uint8_t aCodes[AMB_COUNT]; }; struct mnem opcodes_6502[] = { // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty { "brk", AMM_BRK, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "jsr", AMM_ABS, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rti", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 } }, { "rts", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60 } }, { "ora", AMM_ORA, { 0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d, 0x00, 0x00, 0x00 } }, { "and", AMM_ORA, { 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x00, 0x00, 0x00 } }, { "eor", AMM_ORA, { 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, 0x5d, 0x00, 0x00, 0x00 } }, { "adc", AMM_ORA, { 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x00, 0x00, 0x00 } }, { "sta", AMM_STA, { 0x81, 0x85, 0x00, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0x00, 0x00, 0x00 } }, { "lda", AMM_ORA, { 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0x00, 0x00, 0x00 } }, { "cmp", AMM_ORA, { 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd5, 0xd9, 0xdd, 0x00, 0x00, 0x00 } }, { "sbc", AMM_ORA, { 0xe1, 0xe5, 0xe9, 0xed, 0xf1, 0xf5, 0xf9, 0xfd, 0x00, 0x00, 0x00 } }, { "asl", AMM_ASL, { 0x00, 0x06, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x1e, 0x00, 0x0a, 0x0a } }, { "rol", AMM_ASL, { 0x00, 0x26, 0x00, 0x2e, 0x00, 0x36, 0x00, 0x3e, 0x00, 0x2a, 0x2a } }, { "lsr", AMM_ASL, { 0x00, 0x46, 0x00, 0x4e, 0x00, 0x56, 0x00, 0x5e, 0x00, 0x4a, 0x4a } }, { "ror", AMM_ASL, { 0x00, 0x66, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x7e, 0x00, 0x6a, 0x6a } }, { "stx", AMM_STX, { 0x00, 0x86, 0x00, 0x8e, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ldx", AMM_LDX, { 0x00, 0xa6, 0xa2, 0xae, 0x00, 0xb6, 0x00, 0xbe, 0x00, 0x00, 0x00 } }, { "dec", AMM_DEC, { 0x00, 0xc6, 0x00, 0xce, 0x00, 0xd6, 0x00, 0xde, 0x00, 0x00, 0x00 } }, { "inc", AMM_DEC, { 0x00, 0xe6, 0x00, 0xee, 0x00, 0xf6, 0x00, 0xfe, 0x00, 0x00, 0x00 } }, { "php", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } }, { "plp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28 } }, { "pha", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48 } }, { "pla", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68 } }, { "dey", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88 } }, { "tay", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8 } }, { "iny", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8 } }, { "inx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8 } }, // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty { "bpl", AMM_BRA, { 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bmi", AMM_BRA, { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bvc", AMM_BRA, { 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bvs", AMM_BRA, { 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bcc", AMM_BRA, { 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bcs", AMM_BRA, { 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bne", AMM_BRA, { 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "beq", AMM_BRA, { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "clc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18 } }, { "sec", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38 } }, { "cli", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58 } }, { "sei", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78 } }, { "tya", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98 } }, { "clv", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8 } }, { "cld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8 } }, { "sed", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8 } }, { "bit", AMM_BIT, { 0x00, 0x24, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "jmp", AMM_JMP, { 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00 } }, { "sty", AMM_STY, { 0x00, 0x84, 0x00, 0x8c, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ldy", AMM_LDY, { 0x00, 0xa4, 0xa0, 0xac, 0x00, 0xb4, 0x00, 0xbc, 0x00, 0x00, 0x00 } }, { "cpy", AMM_CPY, { 0x00, 0xc4, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cpx", AMM_CPY, { 0x00, 0xe4, 0xe0, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "txa", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a } }, { "txs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a } }, { "tax", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa } }, { "tsx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba } }, { "dex", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca } }, { "nop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea } }, // 21 ILLEGAL 6502 OPCODES (http://www.oxyron.de/html/opcodes02.html) // NOTE: If adding or removing, update NUM_ILLEGAL_6502_OPS // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty { "slo", AMM_SLO, { 0x03, 0x07, 0x00, 0x0f, 0x13, 0x17, 0x1b, 0x1f, 0x00, 0x00, 0x00 } }, { "rla", AMM_SLO, { 0x23, 0x27, 0x00, 0x2f, 0x33, 0x37, 0x3b, 0x3f, 0x00, 0x00, 0x00 } }, { "sre", AMM_SLO, { 0x43, 0x47, 0x00, 0x4f, 0x53, 0x57, 0x5b, 0x5f, 0x00, 0x00, 0x00 } }, { "rra", AMM_SLO, { 0x63, 0x67, 0x00, 0x6f, 0x73, 0x77, 0x7b, 0x7f, 0x00, 0x00, 0x00 } }, { "sax", AMM_SAX, { 0x83, 0x87, 0x00, 0x8f, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "lax", AMM_LAX, { 0xa3, 0xa7, 0x00, 0xaf, 0xb3, 0xb7, 0x00, 0xbf, 0x00, 0x00, 0x00 } }, { "dcp", AMM_SLO, { 0xc3, 0xc7, 0x00, 0xcf, 0xd3, 0xd7, 0xdb, 0xdf, 0x00, 0x00, 0x00 } }, { "isc", AMM_SLO, { 0xe3, 0xe7, 0x00, 0xef, 0xf3, 0xf7, 0xfb, 0xff, 0x00, 0x00, 0x00 } }, { "anc", AMM_IMM, { 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "aac", AMM_IMM, { 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "alr", AMM_IMM, { 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "arr", AMM_IMM, { 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "xaa", AMM_IMM, { 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, {"lax2", AMM_IMM, { 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "axs", AMM_IMM, { 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "sbi", AMM_IMM, { 0x00, 0x00, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ahx", AMM_AHX, { 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x00, 0x00, 0x00 } }, { "shy", AMM_SHY, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00 } }, { "shx", AMM_SHX, { 0x00, 0x00, 0x00, 0x00, 0x93, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00 } }, { "tas", AMM_SHX, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00 } }, { "las", AMM_SHX, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00 } }, }; const char* aliases_6502[] = { "bcc", "blt", "bcs", "bge", nullptr, nullptr }; uint8_t timing_6502[] = { 0x0e, 0x0c, 0xff, 0xff, 0xff, 0x06, 0x0a, 0xff, 0x06, 0x04, 0x04, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff, 0x0c, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x0a, 0xff, 0x08, 0x04, 0x04, 0xff, 0x08, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff, 0x0c, 0x0c, 0xff, 0xff, 0xff, 0x06, 0x0a, 0xff, 0x06, 0x04, 0x04, 0xff, 0x06, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff, 0x0c, 0x0c, 0xff, 0xff, 0xff, 0x06, 0x0a, 0xff, 0x08, 0x04, 0x04, 0xff, 0x0a, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff, 0xff, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x06, 0xff, 0x04, 0xff, 0x04, 0xff, 0x08, 0x08, 0x08, 0xff, 0x05, 0x0c, 0xff, 0xff, 0x08, 0x08, 0x08, 0xff, 0x04, 0x0a, 0x04, 0xff, 0xff, 0x0a, 0xff, 0xff, 0x04, 0x0c, 0x04, 0xff, 0x06, 0x06, 0x06, 0xff, 0x04, 0x04, 0x04, 0xff, 0x08, 0x08, 0x08, 0xff, 0x05, 0x0b, 0xff, 0xff, 0x08, 0x08, 0x08, 0xff, 0x04, 0x09, 0x04, 0xff, 0x09, 0x09, 0x09, 0xff, 0x04, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x0a, 0xff, 0x04, 0x04, 0x04, 0xff, 0x08, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff, 0x04, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x0a, 0xff, 0x04, 0x04, 0x04, 0xff, 0x08, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff }; static const int num_opcodes_6502 = sizeof(opcodes_6502) / sizeof(opcodes_6502[0]); struct mnem opcodes_65C02[] = { // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs { "brk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "jsr", AMM_ABS, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rti", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00 } }, { "rts", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00 } }, { "ora", AMC_ORA, { 0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00 } }, { "and", AMC_ORA, { 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00 } }, { "eor", AMC_ORA, { 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, 0x5d, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00 } }, { "adc", AMC_ORA, { 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00 } }, { "sta", AMC_STA, { 0x81, 0x85, 0x00, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00 } }, { "lda", AMC_ORA, { 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00 } }, { "cmp", AMC_ORA, { 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd5, 0xd9, 0xdd, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00 } }, { "sbc", AMC_ORA, { 0xe1, 0xe5, 0xe9, 0xed, 0xf1, 0xf5, 0xf9, 0xfd, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00 } }, { "asl", AMM_ASL, { 0x00, 0x06, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x1e, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x00 } }, { "rol", AMM_ASL, { 0x00, 0x26, 0x00, 0x2e, 0x00, 0x36, 0x00, 0x3e, 0x00, 0x2a, 0x2a, 0x00, 0x00, 0x00 } }, { "lsr", AMM_ASL, { 0x00, 0x46, 0x00, 0x4e, 0x00, 0x56, 0x00, 0x5e, 0x00, 0x4a, 0x4a, 0x00, 0x00, 0x00 } }, { "ror", AMM_ASL, { 0x00, 0x66, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x7e, 0x00, 0x6a, 0x6a, 0x00, 0x00, 0x00 } }, { "stx", AMM_STX, { 0x00, 0x86, 0x00, 0x8e, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ldx", AMM_LDX, { 0x00, 0xa6, 0xa2, 0xae, 0x00, 0xb6, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "dec", AMC_DEC, { 0x00, 0xc6, 0x00, 0xce, 0x00, 0xd6, 0x00, 0xde, 0x00, 0x3a, 0x3a, 0x00, 0x00, 0x00 } }, { "inc", AMC_DEC, { 0x00, 0xe6, 0x00, 0xee, 0x00, 0xf6, 0x00, 0xfe, 0x00, 0x1a, 0x1a, 0x00, 0x00, 0x00 } }, { "dea", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00 } }, { "ina", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00 } }, { "php", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 } }, { "plp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 } }, { "pha", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00 } }, { "pla", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00 } }, { "phy", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00 } }, { "ply", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00 } }, { "phx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x00, 0x00, 0x00 } }, { "plx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00 } }, { "dey", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 } }, { "tay", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00 } }, { "iny", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00 } }, { "inx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00 } }, // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs { "bpl", AMM_BRA, { 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bmi", AMM_BRA, { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bvc", AMM_BRA, { 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bvs", AMM_BRA, { 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bra", AMM_BRA, { 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bcc", AMM_BRA, { 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bcs", AMM_BRA, { 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bne", AMM_BRA, { 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "beq", AMM_BRA, { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "clc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00 } }, { "sec", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00 } }, { "cli", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00 } }, { "sei", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00 } }, { "tya", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00 } }, { "clv", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00 } }, { "cld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00 } }, { "sed", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00 } }, { "bit", AMC_BIT, { 0x00, 0x24, 0x89, 0x2c, 0x00, 0x34, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "stz", AMC_STZ, { 0x00, 0x64, 0x00, 0x9c, 0x00, 0x74, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "trb", AMC_TRB, { 0x00, 0x14, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tsb", AMC_TRB, { 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "jmp", AMC_JMP, { 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x7c, 0x00 } }, { "sty", AMM_STY, { 0x00, 0x84, 0x00, 0x8c, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ldy", AMM_LDY, { 0x00, 0xa4, 0xa0, 0xac, 0x00, 0xb4, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cpy", AMM_CPY, { 0x00, 0xc4, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cpx", AMM_CPY, { 0x00, 0xe4, 0xe0, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "txa", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00 } }, { "txs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00 } }, { "tax", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00 } }, { "tsx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00 } }, { "dex", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00 } }, { "nop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00 } }, // WDC specific (18 instructions) // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs { "stp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00 } }, { "wai", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00 } }, { "bbr0", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f } }, { "bbr1", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f } }, { "bbr2", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f } }, { "bbr3", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f } }, { "bbr4", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f } }, { "bbr5", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f } }, { "bbr6", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f } }, { "bbr7", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f } }, { "bbs0", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f } }, { "bbs1", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f } }, { "bbs2", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf } }, { "bbs3", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf } }, { "bbs4", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf } }, { "bbs5", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf } }, { "bbs6", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef } }, { "bbs7", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0xff } }, }; const char* aliases_65C02[] = { "bcc", "blt", "bcs", "bge", nullptr, nullptr }; static const int num_opcodes_65C02 = sizeof(opcodes_65C02) / sizeof(opcodes_65C02[0]); struct mnem opcodes_65816[] = { // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs [zp] [zp],y absl absl,x b,s (b,s),y[$000] b,b { "brk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "jsr", AM8_JSR, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "jsl", AM8_JSL, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rti", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rts", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rtl", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ora", AM8_ORA, { 0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x07, 0x17, 0x0f, 0x1f, 0x03, 0x13, 0x00, 0x00 } }, { "and", AM8_ORA, { 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x27, 0x37, 0x2f, 0x3f, 0x23, 0x33, 0x00, 0x00 } }, { "eor", AM8_ORA, { 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, 0x5d, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x47, 0x57, 0x4f, 0x5f, 0x43, 0x53, 0x00, 0x00 } }, { "adc", AM8_ORA, { 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x67, 0x77, 0x6f, 0x7f, 0x63, 0x73, 0x00, 0x00 } }, { "sta", AM8_STA, { 0x81, 0x85, 0x00, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x87, 0x97, 0x8f, 0x9f, 0x83, 0x93, 0x00, 0x00 } }, { "lda", AM8_ORA, { 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0xa7, 0xb7, 0xaf, 0xbf, 0xa3, 0xb3, 0x00, 0x00 } }, { "cmp", AM8_ORA, { 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd5, 0xd9, 0xdd, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0xc7, 0xd7, 0xcf, 0xdf, 0xc3, 0xd3, 0x00, 0x00 } }, { "sbc", AM8_ORA, { 0xe1, 0xe5, 0xe9, 0xed, 0xf1, 0xf5, 0xf9, 0xfd, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00, 0xe7, 0xf7, 0xef, 0xff, 0xe3, 0xf3, 0x00, 0x00 } }, {"oral", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x1f, 0x00, 0x00, 0x00, 0x00 } }, {"andl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x3f, 0x00, 0x00, 0x00, 0x00 } }, {"eorl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5f, 0x00, 0x00, 0x00, 0x00 } }, {"adcl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x7f, 0x00, 0x00, 0x00, 0x00 } }, {"stal", AM8_STL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x9f, 0x00, 0x00, 0x00, 0x00 } }, {"ldal", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xbf, 0x00, 0x00, 0x00, 0x00 } }, {"cmpl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0xdf, 0x00, 0x00, 0x00, 0x00 } }, {"sbcl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xff, 0x00, 0x00, 0x00, 0x00 } }, { "asl", AMM_ASL, { 0x00, 0x06, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x1e, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rol", AMM_ASL, { 0x00, 0x26, 0x00, 0x2e, 0x00, 0x36, 0x00, 0x3e, 0x00, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "lsr", AMM_ASL, { 0x00, 0x46, 0x00, 0x4e, 0x00, 0x56, 0x00, 0x5e, 0x00, 0x4a, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ror", AMM_ASL, { 0x00, 0x66, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x7e, 0x00, 0x6a, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "stx", AMM_STX, { 0x00, 0x86, 0x00, 0x8e, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ldx", AM8_LDX, { 0x00, 0xa6, 0xa2, 0xae, 0x00, 0xb6, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "dec", AMC_DEC, { 0x00, 0xc6, 0x00, 0xce, 0x00, 0xd6, 0x00, 0xde, 0x00, 0x3a, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "inc", AMC_DEC, { 0x00, 0xe6, 0x00, 0xee, 0x00, 0xf6, 0x00, 0xfe, 0x00, 0x1a, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "dea", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ina", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "php", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "plp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "pha", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs [zp] [zp],y absl absl,x b,s (b,s),y[$0000]b,b { "pla", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "phy", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ply", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "phx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "plx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "dey", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tay", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "iny", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "inx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bpl", AMM_BRA, { 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bmi", AMM_BRA, { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bvc", AMM_BRA, { 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bvs", AMM_BRA, { 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bra", AMM_BRA, { 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "brl", AM8_BRL, { 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bcc", AMM_BRA, { 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bcs", AMM_BRA, { 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bne", AMM_BRA, { 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "beq", AMM_BRA, { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "clc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "sec", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cli", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "sei", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tya", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "clv", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "sed", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "bit", AM8_BIT, { 0x00, 0x24, 0x89, 0x2c, 0x00, 0x34, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "stz", AMC_STZ, { 0x00, 0x64, 0x00, 0x9c, 0x00, 0x74, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "trb", AMC_TRB, { 0x00, 0x14, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tsb", AMC_TRB, { 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, // nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs [zp] [zp],y absl absl,x b,s (b,s),y[$0000]b,b { "jmp", AM8_JMP, { 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xdc, 0x00 } }, { "jml", AM8_JML, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xdc, 0x00 } }, { "sty", AMM_STY, { 0x00, 0x84, 0x00, 0x8c, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "ldy", AM8_LDY, { 0x00, 0xa4, 0xa0, 0xac, 0x00, 0xb4, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cpy", AM8_CPY, { 0x00, 0xc4, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cpx", AM8_CPY, { 0x00, 0xe4, 0xe0, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "txa", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "txs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tax", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tsx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "dex", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "nop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "cop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "wdm", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "mvp", AM8_MVN, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44 } }, { "mvn", AM8_MVN, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54 } }, { "pea", AMM_ABS, { 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "pei", AMM_ZP, { 0x00, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "per", AM8_PER, { 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "rep", AM8_REP, { 0x00, 0xc2, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "sep", AM8_REP, { 0x00, 0xe2, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "phd", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tcs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "pld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tsc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "phk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tcd", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tdc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "phb", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "txy", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "plb", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "tyx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "wai", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "stp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "xba", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, { "xce", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, }; const char* aliases_65816[] = { "bcc", "blt", "bcs", "bge", "tcs", "tas", "tsc", "tsa", "xba", "swa", "tcd", "tad", "tdc", "tda", nullptr, nullptr }; static const int num_opcodes_65816 = sizeof(opcodes_65816) / sizeof(opcodes_65816[0]); uint8_t timing_65816[] = { 0x4e, 0x1c, 0x4e, 0x28, 0x3a, 0x26, 0x3a, 0x1c, 0x46, 0x24, 0x44, 0x48, 0x4c, 0x28, 0x5c, 0x2a, 0x44, 0x1a, 0x1a, 0x2e, 0x3a, 0x18, 0x6c, 0x1c, 0x44, 0x28, 0x44, 0x44, 0x4c, 0x28, 0x5e, 0x2a, 0x4c, 0x1c, 0x50, 0x28, 0x16, 0x26, 0x3a, 0x1c, 0x48, 0x24, 0x44, 0x4a, 0x28, 0x28, 0x4c, 0x2a, 0x44, 0x1a, 0x1a, 0x2e, 0x18, 0x18, 0x3c, 0x1c, 0x44, 0x28, 0x44, 0x44, 0x28, 0x28, 0x4e, 0x2a, 0x4c, 0x1c, 0x42, 0x28, 0x42, 0x16, 0x6a, 0x1c, 0x26, 0x24, 0x44, 0x46, 0x46, 0x28, 0x5c, 0x2a, 0x44, 0x1a, 0x1a, 0x2e, 0x42, 0x18, 0x6c, 0x1c, 0x44, 0x28, 0x76, 0x44, 0x48, 0x28, 0x5e, 0x2a, 0x4c, 0x1c, 0x4c, 0x28, 0x16, 0x26, 0x3a, 0x1c, 0x28, 0x24, 0x44, 0x4c, 0x4a, 0x28, 0x4c, 0x2a, 0x44, 0x1a, 0x1a, 0x2e, 0x28, 0x18, 0x3c, 0x1c, 0x44, 0x28, 0x78, 0x44, 0x4c, 0x28, 0x4e, 0x2a, 0x46, 0x1c, 0x48, 0x28, 0x86, 0x16, 0x86, 0x1c, 0x44, 0x24, 0x44, 0x46, 0x78, 0x28, 0x78, 0x2a, 0x44, 0x1c, 0x1a, 0x2e, 0x88, 0x18, 0x88, 0x1c, 0x44, 0x2a, 0x44, 0x44, 0x28, 0x2a, 0x2a, 0x2a, 0x74, 0x1c, 0x74, 0x28, 0x86, 0x16, 0x86, 0x1c, 0x44, 0x24, 0x44, 0x48, 0x78, 0x28, 0x78, 0x2a, 0x44, 0x1a, 0x1a, 0x2e, 0x88, 0x18, 0x88, 0x1c, 0x44, 0x28, 0x44, 0x44, 0x78, 0x28, 0x78, 0x2a, 0x74, 0x1c, 0x46, 0x28, 0x86, 0x16, 0x6a, 0x1c, 0x44, 0x24, 0x44, 0x26, 0x78, 0x28, 0x5c, 0x2a, 0x44, 0x1a, 0x1a, 0x2e, 0x4c, 0x18, 0x6c, 0x1c, 0x44, 0x28, 0x76, 0x46, 0x4c, 0x28, 0x5e, 0x2a, 0x74, 0x3c, 0x46, 0x48, 0x86, 0x36, 0x6a, 0x3c, 0x44, 0x44, 0x44, 0x46, 0x78, 0x48, 0x5c, 0x4a, 0x44, 0x3a, 0x3a, 0x4e, 0x4a, 0x38, 0x6c, 0x3c, 0x44, 0x48, 0x78, 0x44, 0x50, 0x48, 0x5e, 0x4a }; // m=0, i=0, dp!=0 uint8_t timing_65816_plus[9][3] = { { 0, 0, 0 }, // 6502 plus timing check bit 0 { 1, 0, 1 }, // acc 16 bit + dp!=0 { 1, 0, 0 }, // acc 16 bit { 0, 0, 1 }, // dp != 0 { 0, 0, 0 }, // no plus { 2, 0, 0 }, // acc 16 bit yields 2+ { 2, 0, 1 }, // acc 16 bit yields 2+ + dp!=0 { 0, 1, 0 }, // idx 16 bit { 0, 1, 1 } // idx 16 bit + dp!=0 }; // 65C02 // http://6502.org/tutorials/65c02opcodes.html // http://www.oxyron.de/html/opcodesc02.html // 65816 // http://wiki.superfamicom.org/snes/show/65816+Reference#fn:14 // http://softpixel.com/~cwright/sianse/docs/65816NFO.HTM // http://www.oxyron.de/html/opcodes816.html // How instruction argument is encoded enum CODE_ARG { CA_NONE, // single byte instruction CA_ONE_BYTE, // instruction carries one byte CA_TWO_BYTES, // instruction carries two bytes CA_THREE_BYTES, // instruction carries three bytes CA_BRANCH, // instruction carries an 8 bit relative address CA_BRANCH_16, // instruction carries a 16 bit relative address CA_BYTE_BRANCH, // instruction carries one byte and one branch CA_TWO_ARG_BYTES, // two separate values }; enum CPUIndex { CPU_6502, CPU_6502_ILLEGAL, CPU_65C02, CPU_65C02_WDC, CPU_65816 }; // CPU by index struct CPUDetails { mnem *opcodes; int num_opcodes; const char* name; const char** aliases; const uint8_t *timing; } aCPUs[] = { { opcodes_6502, num_opcodes_6502 - NUM_ILLEGAL_6502_OPS, "6502", aliases_6502, timing_6502 }, { opcodes_6502, num_opcodes_6502, "6502ill", aliases_6502, timing_6502 }, { opcodes_65C02, num_opcodes_65C02 - NUM_WDC_65C02_SPECIFIC_OPS, "65C02", aliases_65C02, nullptr }, { opcodes_65C02, num_opcodes_65C02, "65C02WDC", aliases_65C02, nullptr }, { opcodes_65816, num_opcodes_65816, "65816", aliases_65816, timing_65816 }, }; static const int nCPUs = sizeof(aCPUs) / sizeof(aCPUs[0]); // hardtexted strings static const strref c_comment("//"); static const strref word_char_range("!0-9a-zA-Z_@$!#"); static const strref macro_arg_bookend("!0-9a-zA-Z_@$!."); static const strref label_end_char_range("!0-9a-zA-Z_@$!.!:"); static const strref label_end_char_range_merlin("!0-9a-zA-Z_@$]:?"); static const strref filename_end_char_range("!0-9a-zA-Z_!@#$%&()/\\-."); static const strref keyword_equ("equ"); static const strref str_label("label"); static const strref str_const("const"); static const strref struct_byte("byte"); static const strref struct_word("word"); static const strref import_source("source"); static const strref import_binary("binary"); static const strref import_c64("c64"); static const strref import_text("text"); static const strref import_object("object"); static const strref import_symbols("symbols"); static const strref pool_subpool("pool"); static const char* aAddrModeFmt[] = { "%s ($%02x,x)", // 00 "%s $%02x", // 01 "%s #$%02x", // 02 "%s $%04x", // 03 "%s ($%02x),y", // 04 "%s $%02x,x", // 05 "%s $%04x,y", // 06 "%s $%04x,x", // 07 "%s ($%04x)", // 08 "%s A", // 09 "%s ", // 0a "%s ($%02x)", // 0b "%s ($%04x,x)", // 0c "%s $%02x, $%04x", // 0d "%s [$%02x]", // 0e "%s [$%02x],y", // 0f "%s $%06x", // 10 "%s $%06x,x", // 11 "%s $%02x,s", // 12 "%s ($%02x,s),y", // 13 "%s [$%04x]", // 14 "%s $%02x,$%02x", // 15 }; static const char *str_section_type[] = { "UNDEFINED", // not set "CODE", // default type "DATA", // data section (matters for GS/OS OMF) "BSS", // uninitialized data section "ZEROPAGE" // uninitialized data section in zero page / direct page }; static const int num_section_type_str = sizeof(str_section_type) / sizeof(str_section_type[0]); typedef struct sDirectiveName { const char *name; AssemblerDirective directive; } DirectiveName; DirectiveName aDirectiveNames[] { { "CPU", AD_CPU }, { "PROCESSOR", AD_CPU }, { "PC", AD_ORG }, { "ORG", AD_ORG }, { "LOAD", AD_LOAD }, { "EXPORT", AD_EXPORT }, { "SECTION", AD_SECTION }, { "SEG", AD_SECTION }, // DASM version of SECTION { "SEGMENT", AD_SECTION }, // CA65 version of SECTION { "MERGE", AD_MERGE }, { "LINK", AD_LINK }, { "XDEF", AD_XDEF }, { "XREF", AD_XREF }, { "INCOBJ", AD_INCOBJ }, { "ALIGN", AD_ALIGN }, { "MACRO", AD_MACRO }, { "MAC", AD_MACRO }, // MERLIN { "EVAL", AD_EVAL }, { "PRINT", AD_EVAL }, { "ECHO", AD_EVAL }, // DASM version of EVAL/PRINT { "BYTE", AD_BYTES }, { "BYTES", AD_BYTES }, { "WORD", AD_WORDS }, { "WORDS", AD_WORDS }, { "LONG", AD_ADRL }, { "DC", AD_DC }, { "DV", AD_DC }, // DASM variation of DC which allows expressions { "TEXT", AD_TEXT }, { "INCLUDE", AD_INCLUDE }, { "INCBIN", AD_INCBIN }, { "IMPORT", AD_IMPORT }, { "CONST", AD_CONST }, { "LABEL", AD_LABEL }, { "STRING", AD_STRING }, { "FUNCTION", AD_FUNCTION }, { "UNDEF", AD_UNDEF }, { "INCSYM", AD_INCSYM }, { "LABPOOL", AD_LABPOOL }, { "POOL", AD_LABPOOL }, { "IF", AD_IF }, { "IFDEF", AD_IFDEF }, { "IFNDEF", AD_IFNDEF }, { "IFCONST", AD_IFCONST }, { "IFBLANK", AD_IFBLANK }, // #IFBLANK: Conditional assembly follows based on rest of line empty { "IFNBLANK", AD_IFNBLANK }, // #IFDEF: Conditional assembly follows based on rest of line not empty { "ELSE", AD_ELSE }, { "ELIF", AD_ELIF }, { "ENDIF", AD_ENDIF }, { "STRUCT", AD_STRUCT }, { "ENUM", AD_ENUM }, { "REPT", AD_REPT }, { "REPEAT", AD_REPT }, // ca65 version of rept { "INCDIR", AD_INCDIR }, { "A16", AD_A16 }, // A16: Set 16 bit accumulator mode { "A8", AD_A8 }, // A8: Set 8 bit accumulator mode { "XY16", AD_XY16 }, // XY16: Set 16 bit index register mode { "XY8", AD_XY8 }, // XY8: Set 8 bit index register mode { "I16", AD_XY16 }, // I16: Set 16 bit index register mode { "I8", AD_XY8 }, // I8: Set 8 bit index register mode { "DUMMY", AD_DUMMY }, { "DUMMY_END", AD_DUMMY_END }, { "DS", AD_DS }, // Define space { "RES", AD_DS }, // Reserve space { "SCOPE", AD_SCOPE }, // SCOPE: Begin ca65 style scope { "ENDSCOPE", AD_ENDSCOPE },// ENDSCOPR: End ca65 style scope { "PUSH", AD_PUSH }, { "PULL", AD_PULL }, { "ABORT", AD_ABORT }, { "ERR", AD_ABORT }, // DASM version of ABORT }; // Merlin specific directives separated from regular directives to avoid confusion DirectiveName aDirectiveNamesMerlin[] { { "MX", AD_MX }, // MERLIN { "STR", AD_LNK }, // MERLIN { "DA", AD_WORDS }, // MERLIN { "DW", AD_WORDS }, // MERLIN { "ASC", AD_TEXT }, // MERLIN { "PUT", AD_INCLUDE }, // MERLIN { "DDB", AD_WORDS }, // MERLIN { "DB", AD_BYTES }, // MERLIN { "DFB", AD_BYTES }, // MERLIN { "HEX", AD_HEX }, // MERLIN { "DO", AD_IF }, // MERLIN { "FIN", AD_ENDIF }, // MERLIN { "EJECT", AD_EJECT }, // MERLIN { "OBJ", AD_EJECT }, // MERLIN { "TR", AD_EJECT }, // MERLIN { "END", AD_EJECT }, // MERLIN { "REL", AD_EJECT }, // MERLIN { "USR", AD_USR }, // MERLIN { "DUM", AD_DUMMY }, // MERLIN { "DEND", AD_DUMMY_END }, // MERLIN { "LST", AD_LST }, // MERLIN { "LSTDO", AD_LST }, // MERLIN { "LUP", AD_REPT }, // MERLIN { "SAV", AD_SAV }, // MERLIN { "DSK", AD_SAV }, // MERLIN { "LNK", AD_LNK }, // MERLIN { "XC", AD_XC }, // MERLIN { "ENT", AD_ENT }, // MERLIN (xdef, but label on same line) { "EXT", AD_EXT }, // MERLIN (xref, which are implied in x65 object files) { "ADR", AD_ADR }, // ADR: MERLIN store 3 byte word { "ADRL", AD_ADRL }, // ADRL: MERLIN store 4 byte word { "CYC", AD_CYC }, // MERLIN: Start and stop cycle counter }; struct EvalFuncNames { const char* name; EvalFuncs function; }; EvalFuncNames aEvalFunctions[] = { { "DEFINED", EF_DEFINED }, // DEFINED(label) 1 if label is defined { "DEF", EF_DEFINED }, // DEFINED(label) 1 if label is defined { "REFERENCED", EF_REFERENCED }, // REFERENCED(label) 1 if label has been referenced in this file { "BLANK", EF_BLANK }, // BLANK() 1 if the contents within the parenthesis is empty { "CONST", EF_CONST }, // CONST(label) 1 if label is a const label { "SIZEOF", EF_SIZEOF}, // SIZEOF(struct) returns size of structs { "TRIGSIN", EF_SIN }, // TRIGSIN(index, period, amplitude) }; static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]); static const int nDirectiveNamesMerlin = sizeof(aDirectiveNamesMerlin) / sizeof(aDirectiveNamesMerlin[0]); static const int nEvalFuncs = sizeof(aEvalFunctions) / sizeof(aEvalFunctions[0]); // Binary search over an array of unsigned integers, may contain multiple instances of same key uint32_t FindLabelIndex(uint32_t hash, uint32_t *table, uint32_t count) { uint32_t max = count; uint32_t first = 0; while (count!=first) { int index = (first+count)/2; uint32_t read = table[index]; if (hash==read) { while (index && table[index-1]==hash) index--; // guarantee first identical index returned on match return index; } else if (hash>read) first = index+1; else count = index; } if (count<max && table[count]<hash) count++; else if (count && table[count-1]>hash) count--; return count; } // // // ASSEMBLER STATE // // // pairArray is basically two vectors sharing a size without constructors on growth or insert template <class H, class V> class pairArray { protected: H *keys; V *values; uint32_t _count; uint32_t _capacity; public: pairArray() : keys(nullptr), values(nullptr), _count(0), _capacity(0) {} void reserve(uint32_t 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(uint32_t 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(uint32_t pos, H key) { if (insert(pos) && keys) { keys[pos] = key; return true; } return false; } void remove(uint32_t 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(uint32_t pos) { return keys[pos]; } V* getValues() { return values; } V& getValue(uint32_t pos) { return values[pos]; } uint32_t count() const { return _count; } uint32_t capacity() const { return _capacity; } void clear() { if (keys!=nullptr) free(keys); keys = nullptr; if (values!=nullptr) free(values); values = nullptr; _capacity = 0; _count = 0; } }; template< class KeyType, class ValueType, class CountType = size_t > struct HashTable { CountType size, maxSteps, used; KeyType* keys; ValueType* values; static CountType HashFunction(KeyType v) { return CountType(((v + (v >> 27) + (v << 29)) + 14695981039346656037) * 1099511628211); } static CountType HashIndex(KeyType hash, CountType tableSize) { return hash & (tableSize - 1); } static CountType GetNextIndex(KeyType hash, CountType tableSize) { return (hash + 1) & (tableSize - 1); } static CountType KeyToIndex(KeyType key, CountType tableSize) { return HashIndex(HashFunction(key), tableSize); } static CountType FindKeyIndex(KeyType hash, CountType hashTableSize, KeyType* hashKeys, CountType maxKeySteps) { CountType index = KeyToIndex(hash, hashTableSize); while (hashKeys) { KeyType key = hashKeys[index]; if (!key || key == hash) { return index; } index = GetNextIndex(index, hashTableSize); if (!maxKeySteps--) { break; } } return index; } CountType KeyToIndex(KeyType key) { return KeyToIndex(key, size); } CountType InsertKey(KeyType key, CountType index) { const KeyType* hashKeys = keys; CountType currSize = size; CountType insertSteps = 0; while (KeyType k = hashKeys[index]) { if (k == key) { return index; } // key already exists CountType kfirst = KeyToIndex(k, currSize); CountType ksteps = kfirst > index ? (currSize + index - kfirst) : (index - kfirst); if (insertSteps > ksteps) { return index; } index = GetNextIndex(index, size); ++insertSteps; } return index; } CountType FindKeyIndex(KeyType hash) const { return FindKeyIndex(hash, size, keys, maxSteps); } CountType Steps(KeyType hash) { CountType slot = KeyToIndex(hash, size); CountType numSteps = 0; while (keys[slot] && keys[slot] != hash) { ++numSteps; slot = GetNextIndex(slot, size); } return numSteps; } void UpdateSteps(CountType first, CountType slot) { CountType steps = slot > first ? (slot - first) : (size + slot - first); if (steps > maxSteps) { maxSteps = steps; } } ValueType* InsertFitted(KeyType key) { assert(key); // key may not be 0 CountType first = KeyToIndex(key); CountType slot = InsertKey(key, first); UpdateSteps(first, slot); if (keys[slot]) { if (keys[slot] == key) { return &values[slot]; } else { KeyType prvKey = keys[slot]; ValueType prev_value = values[slot]; keys[slot] = key; for (;; ) { CountType prev_first = KeyToIndex(prvKey); CountType slotRH = InsertKey(prvKey, prev_first); UpdateSteps(prev_first, slotRH); if (keys[slotRH] && keys[slotRH] != prvKey) { KeyType tmpKey = keys[slotRH]; keys[slotRH] = prvKey; prvKey = tmpKey; ValueType temp_value = values[slotRH]; values[slotRH] = prev_value; prev_value = temp_value; } else { keys[slotRH] = prvKey; values[slotRH] = prev_value; ++used; return &values[slot]; } } } } keys[slot] = key; ++used; return &values[slot]; } HashTable() { Reset(); } void Reset() { used = 0; size = 0; maxSteps = 0; keys = nullptr; values = nullptr; } ~HashTable() { Clear(); } void Clear() { if (values) { for (CountType i = 0, n = size; i < n; ++i) { values[i].~ValueType(); } free(values); } if (keys) { free(keys); } Reset(); } CountType GetUsed() const { return used; } bool TableMax() const { return used && (used << 4) >= (size * 13); } void Grow() { KeyType *prevKeys = keys; ValueType *prevValues = values; CountType prevSize = size, newSize = prevSize ? (prevSize << 1) : 64; size = newSize; keys = (KeyType*)calloc(1, newSize * sizeof(KeyType)); values = (ValueType*)calloc(1, newSize * sizeof(ValueType)); maxSteps = 0; for (CountType i = 0; i < newSize; ++i) { new (values + i) ValueType; } if (used) { used = 0; for (CountType i = 0; i < prevSize; i++) { if (KeyType key = prevKeys[i]) { *InsertFitted(key) = prevValues[i]; } } } if (prevKeys) { free(prevKeys); } if (prevValues) { for (CountType i = 0; i != prevSize; ++i) { prevValues[i].~ValueType(); } free(prevValues); } } ValueType* InsertKey(KeyType key) { if (!size || TableMax()) { Grow(); } return InsertFitted(key); } ValueType* InsertKeyValue(KeyType key, ValueType& value) { ValueType* value_ptr = InsertKey(key); *value_ptr = value; return value_ptr; } bool KeyExists(KeyType key) { return size && key && keys[FindKeyIndex(key)] == key; } ValueType* GetValue(KeyType key) { if (size && key) { CountType slot = FindKeyIndex(key); if (keys[slot] == key) { return &values[slot]; } } return nullptr; } }; // relocs are cheaper than full expressions and work with // local labels for relative sections which would otherwise // be out of scope at link time. struct Reloc { int base_value; int section_offset; // offset into this section int target_section; // which section does this reloc target? int8_t bytes; // number of bytes to write int8_t shift; // number of bits to shift to get value Reloc() : base_value(0), section_offset(-1), target_section(-1), bytes(0), shift(0) {} Reloc(int base, int offs, int sect, int8_t num_bytes, int8_t bit_shift) : base_value(base), section_offset(offs), target_section(sect), bytes(num_bytes), shift(bit_shift) {} }; typedef std::vector<struct Reloc> relocList; // For assembly listing this remembers the location of each line struct ListLine { enum Flags { MNEMONIC = 0x01, KEYWORD = 0x02, CYCLES_START = 0x04, CYCLES_STOP = 0x08, }; strref source_name; // source file index name strref code; // line of code this represents int address; // start address of this line int size; // number of bytes generated for this line int line_offs; // offset into code int flags; // only output code if generated by code bool wasMnemonic() const { return !!(flags & MNEMONIC); } bool startClock() const { return !!(flags & CYCLES_START); } bool stopClock() const { return !!(flags & CYCLES_STOP); } }; typedef std::vector<struct ListLine> Listing; // Source level debugging info that can be saved into linkable object files, this is close to ListLine so possibly combinable. // this belongs in each section so it can be saved with that into the x65 files or generated if linked struct SourceDebugEntry { int source_file_index; // index into Assembler::source_file vector int address; // local address in section int size; int source_file_offset; // can be converted into line/column while linking }; typedef std::vector<struct SLDEntry> SourceDebug; enum SectionType : int8_t { // enum order indicates fixed address linking priority ST_UNDEFINED, // not set ST_CODE, // default type ST_DATA, // data section (matters for GS/OS OMF) ST_BSS, // uninitialized data section ST_ZEROPAGE, // uninitialized data section in zero page / direct page ST_REMOVED // removed, don't export to object file }; // String data typedef struct sStringSymbols { public: strref string_name; // name of the string strref string_const; // string contents if source reference strovl string_value; // string contents if modified, initialized to null string StatusCode Append(strref append); strref get() { return string_value.valid() ? string_value.get_strref() : string_const; } void clear() { if (string_value.cap()) { free(string_value.charstr()); string_value.invalidate(); string_value.clear(); } string_const.clear(); } } StringSymbol; // start of data section support // Default is a relative section // 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 strref export_append; // append this name to export of file strref include_from; // which file did this section originate from? // generated address status int load_address; // if assigned a load address int start_address; int address; // relative or absolute PC int align_address; // for relative sections that needs alignment // merged sections int merged_at; // merged into a section at this offset int merged_into; // -1 if not merged otherwise section merged into int merged_size; // how many bytes were merged in // data output uint8_t *output; // memory for this section uint8_t *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 SourceDebug *pSrcDbg; // if source level debugging info generated // grouped sections int next_group; // next section of a group of relative sections or -1 int first_group; // >=0 if another section is grouped with this section bool address_assigned; // address is absolute if assigned bool dummySection; // true if section does not generate data, only labels SectionType type; // distinguishing section type for relocatable output void reset() { // explicitly cleaning up sections, not called from Section destructor name.clear(); export_append.clear(); include_from.clear(); start_address = address = load_address = 0x0; type = ST_CODE; address_assigned = false; output = nullptr; curr = nullptr; dummySection = false; output_capacity = 0; merged_at = -1; merged_into = -1; merged_size = 0; align_address = 1; if (pRelocs) delete pRelocs; next_group = first_group = -1; pRelocs = nullptr; if (pListing) delete pListing; pListing = nullptr; } void Cleanup() { if (output) free(output); reset(); } bool empty() const { return type != ST_REMOVED && curr==output; } bool unused() const { return !address_assigned && address == start_address; } int DataOffset() const { return int(curr - output); } int size() const { return (int)(curr - output); } int addr_size() const { return address - start_address; } const uint8_t *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; type = ST_BSS; } bool IsDummySection() const { return dummySection; } bool IsRelativeSection() const { return address_assigned == false; } bool IsMergedSection() const { return false; } void AddReloc(int base, int offset, int section, int8_t bytes, int8_t shift); 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() { } // Append data to a section StatusCode CheckOutputCapacity(uint32_t addSize); void AddByte(int b); void AddWord(int w); void AddTriple(int l); void AddBin(const uint8_t *p, int size); void AddText(strref line, strref text_prefix); void AddIndexText(StringSymbol * strSym, strref text); void SetByte(size_t offs, int b) { output[offs] = (uint8_t)b; } void SetWord(size_t offs, int w) { output[offs] = (uint8_t)w; output[offs+1] = uint8_t(w>>8); } void SetTriple(size_t offs, int w) { output[offs] = (uint8_t)w; output[offs+1] = uint8_t(w>>8); output[offs+2] = uint8_t(w>>16); } void SetQuad(size_t offs, int w) { output[offs] = (uint8_t)w; output[offs+1] = uint8_t(w>>8); output[offs+2] = uint8_t(w>>16); output[offs+3] = uint8_t(w>>24); } } Section; // Symbol list entry (in order of parsing) struct MapSymbol { strref name; // string name int value; int16_t section; bool local; // local variables }; typedef std::vector<struct MapSymbol> MapSymbolArray; // Data related to a label typedef struct sLabel { 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 bool reference; // this label is accessed from external and can't be used for evaluation locally bool referenced; // this label has been found via GetLabel and can be assumed to be referenced for some purpose } Label; // If an expression can't be evaluated immediately, this is required // to reconstruct the result when it can be. typedef struct sLateEval { 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_ABS_L_REF, // calculate a bank + absolute address and store at 0, +1, +2 LET_ABS_4_REF, // calculate a 32 bit number LET_BRANCH, // calculate a branch offset and store at this address LET_BRANCH_16, // calculate a branch offset of 16 bits 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 scope_depth; // relevant for scope end int16_t section; // which section to apply to. int16_t rept; // value of rept int file_ref; // -1 if current or xdef'd otherwise index of file for label char* expression_mem; // if a temporary string was used for the expression 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 sMacro { strref name; strref macro; strref source_name; // source file name (error output) strref source_file; // entire source file (req. for line #) bool params_first_line; // the first line of this macro are parameters } Macro; // All local labels are removed when a global label is defined but some when a scope ends typedef struct sLocalLabelRecord { 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 sLabelPool { strref pool_name; int16_t numRanges; // normally 1 range, support multiple for ease of use int16_t depth; // Required for scope closure cleanup uint32_t start; uint32_t end; uint32_t scopeUsed[MAX_SCOPE_DEPTH][2]; // last address assigned + scope depth StatusCode Reserve(uint32_t numBytes, uint32_t &ret_addr, uint16_t scope); void ExitScope(uint16_t scope); } LabelPool; // One member of a label struct struct MemberOffset { uint16_t offset; uint32_t name_hash; strref name; strref sub_struct; }; // Label struct typedef struct sLabelStruct { strref name; uint16_t first_member; uint16_t numMembers; uint16_t size; } LabelStruct; // object file labels that are not xdef'd end up here struct ExtLabels { pairArray<uint32_t, Label> labels; }; // EvalExpression needs a location reference to work out some addresses 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 scope_depth; // scope depth for eval (must match current for scope_end_pc to eval) int relative_section; // return can be relative to this section int file_ref; // can access private label from this file or -1 int rept_cnt; // current repeat counter int recursion; // track recursion depth StatusCode internalErr; // if an error occured during an internal stage of evaluation EvalContext() : pc(0), scope_pc(0), scope_end_pc(0), scope_depth(0), relative_section(-1), file_ref(-1), rept_cnt(0), recursion(0), internalErr(STATUS_OK) {} EvalContext(int _pc, int _scope, int _close, int _sect, int _rept_cnt) : pc(_pc), scope_pc(_scope), scope_end_pc(_close), scope_depth(-1), relative_section(_sect), file_ref(-1), rept_cnt(_rept_cnt), recursion(0), internalErr(STATUS_OK) {} }; // Source context is current file (include file, etc.) or current macro. typedef struct sSourceContext { 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 int16_t repeat; // how many times to repeat this code segment int16_t repeat_total; // initial number of repeats for this code segment int16_t conditional_ctx; // conditional depth at root of this context 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<SourceContext> stack; // stack of contexts SourceContext *currContext; // current context public: ContextStack() : currContext(nullptr) { stack.reserve(32); } SourceContext& curr() { return *currContext; } const SourceContext& curr() const { 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 = (int16_t)rept; context.repeat_total = (int16_t)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; } bool empty() const { return stack.size() == 0; } }; // Support for the PULL and PUSH directives typedef union { int value; char* string; } ValueOrString; typedef std::vector < ValueOrString > SymbolStack; class SymbolStackTable : public HashTable< uint64_t, SymbolStack* > { public: void PushSymbol(Label* symbol); StatusCode PullSymbol(Label* symbol); void PushSymbol(StringSymbol* string); StatusCode PullSymbol(StringSymbol* string); ~SymbolStackTable(); }; // user declared functions struct UserFunction { const char* name; const char* params; const char* expression; }; class UserFunctionMap : public HashTable<uint64_t, UserFunction*> { public: UserFunction *Get(strref name); StatusCode Add(strref name, strref params, strref expresion); ~UserFunctionMap(); }; // The state of the assembler class Asm { public: pairArray<uint32_t, Label> labels; pairArray<uint32_t, StringSymbol> strings; pairArray<uint32_t, Macro> macros; pairArray<uint32_t, LabelPool> labelPools; pairArray<uint32_t, LabelStruct> labelStructs; pairArray<uint32_t, strref> xdefs; // labels matching xdef names will be marked as external std::vector<char*> source_files; // all source files encountered while assembling. referenced by source level debugging std::vector<LateEval> lateEval; std::vector<LocalLabelRecord> localLabels; std::vector<char*> loadedData; // free when assembler is completed std::vector<MemberOffset> structMembers; // labelStructs refer to sets of structMembers std::vector<strref> includePaths; std::vector<Section> allSections; std::vector<ExtLabels> externals; // external labels organized by object file MapSymbolArray map; SymbolStackTable symbolStacks; // enable push/pull of symbols UserFunctionMap userFunctions; // user defined expression functions // CPU target struct mnem *opcode_table; int opcode_count; CPUIndex cpu, list_cpu; OPLookup aInstructions[MAX_OPCODES_DIRECTIVES]; int num_instructions; int default_org; // context for macros / include files ContextStack contextStack; // Current section Section *current_section; // Special syntax rules AsmSyntax syntax; // Conditional assembly vars int conditional_depth; // conditional depth / base depth for context strref conditional_source[MAX_CONDITIONAL_DEPTH]; // start of conditional for error report int8_t conditional_nesting[MAX_CONDITIONAL_DEPTH]; bool conditional_consumed[MAX_CONDITIONAL_DEPTH]; // Scope info int scope_address[MAX_SCOPE_DEPTH]; int scope_depth; int brace_depth; // scope depth defined only by braces, not files strref export_base_name; // binary output name if available strref last_label; // most recently defined label for Merlin macro // ca65 style scope (for now treat global symbols as local symbols, no outside name lookup) int directive_scope_depth; // Eval relative result (only valid if EvalExpression returns STATUS_RELATIVE_SECTION) int lastEvalSection; int lastEvalValue; int8_t lastEvalShift; int8_t list_flags; // listing flags accumulating for each line bool accumulator_16bit; // 65816 specific software dependent immediate mode bool index_reg_16bit; // -"- int8_t cycle_counter_level; // merlin toggles the cycle counter rather than hierarchically evals bool error_encountered; // if any error encountered, don't export binary bool list_assembly; // generate assembler listing bool end_macro_directive; // whether to use { } or macro / endmacro for macro scope bool import_means_xref; // Convert source to binary void Assemble(strref source, strref filename, bool obj_target); // Push a new context and handle enter / exit of context StatusCode PushContext(strref src_name, strref src_file, strref code_seg, int rept = 1); StatusCode PopContext(); // Generate assembler listing if requested bool List(strref filename); // Mimic TASS listing bool ListTassStyle( strref filename ); // Generate source for all valid instructions and addressing modes for current CPU bool AllOpcodes(strref filename); // Clean up memory allocations, reset assembler state void Cleanup(); // Make sure there is room to write more code StatusCode CheckOutputCapacity(uint32_t addSize); // Operations on current section void SetSection(strref name, int address); // fixed address section void SetSection(strref name); // relative address section void LinkLabelsToAddress(int section_id, int section_new, int section_address); StatusCode LinkRelocs(int section_id, int section_new, int section_address); StatusCode AssignAddressToSection(int section_id, int address); StatusCode LinkSections(strref name); // link relative address sections with this name here StatusCode MergeSections(int section_id, int section_merge); // Combine the result of a section onto another StatusCode MergeSectionsByName(int first_section); StatusCode MergeAllSections(int first_section); void DummySection(int address); // non-data section (fixed) void DummySection(); // non-data section (relative) void EndSection(); // pop current section Section& CurrSection() { return *current_section; } void AssignAddressToGroup(); // Merlin LNK support uint8_t* BuildExport(strref append, int &file_size, int &addr); int GetExportNames(strref *aNames, int maxNames); StatusCode LinkZP(); int SectionId() { return int(current_section - &allSections[0]); } int SectionId(Section &s) { return (int)(&s - &allSections[0]); } void AddByte(int b) { CurrSection().AddByte(b); } void AddWord(int w) { CurrSection().AddWord(w); } void AddTriple(int l) { CurrSection().AddTriple(l); } void AddBin(const uint8_t *p, int size) { CurrSection().AddBin(p, size); } // Object file handling StatusCode WriteObjectFile(strref filename); // write x65 object file StatusCode ReadObjectFile(strref filename, int link_to_section = -1); // read x65 object file // Apple II GS OMF StatusCode WriteA2GS_OMF(strref filename, bool full_collapse); // Scope management StatusCode EnterScope(); StatusCode ExitScope(); // 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); // determine a value from a user function with given parameters int EvalUserFunction(UserFunction* user, strref params, EvalContext& etx); // Check if function is a valid function and if so evaluate the expression bool EvalFunction(strref function, strref &expression, EvalContext& etx, int &value); // Calculate a value based on an expression. EvalOperator RPNToken_Merlin(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, int16_t §ion, int &value); EvalOperator RPNToken(strref &expression, EvalContext &etx, EvalOperator prev_op, int16_t §ion, int &value, strref &subexp); StatusCode EvalExpression(strref expression, struct EvalContext &etx, int &result); char* PartialEval( strref expression ); void SetEvalCtxDefaults( struct EvalContext &etx ); int ReptCnt() const; // Access labels Label * GetLabel(strref label, bool reference_check = false); Label* GetLabel(strref label, int file_ref); Label* AddLabel(uint32_t 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); StatusCode IncludeSymbols(strref line); // Strings StringSymbol *GetString(strref string_name); StringSymbol *AddString(strref string_name, strref string_value); StatusCode StringAction(StringSymbol *pStr, strref line); StatusCode ParseStringOp(StringSymbol *pStr, strref line); // Manage locals void MarkLabelLocal(strref label, bool scope_label = false); StatusCode FlushLocalLabels(int scope_exit = -1); // Label pools LabelPool* GetLabelPool(strref pool_name); StatusCode AddLabelPool(strref name, strref args); StatusCode AssignPoolLabel(LabelPool &pool, strref args); // 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, bool missing_is_error = false); // Assembler Directives StatusCode ApplyDirective(AssemblerDirective dir, strref line, strref source_file); StatusCode Directive_Rept(strref line); StatusCode Directive_Macro(strref line); StatusCode Directive_String(strref line); StatusCode Directive_Function(strref line); StatusCode Directive_Undef(strref line); StatusCode Directive_Include(strref line); StatusCode Directive_Incbin(strref line, int skip=0, int len=0); StatusCode Directive_Import(strref line); StatusCode Directive_ORG(strref line); StatusCode Directive_LOAD(strref line); StatusCode Directive_MERGE(strref line); StatusCode Directive_LNK(strref line); StatusCode Directive_XDEF(strref line); StatusCode Directive_XREF(strref label); StatusCode Directive_DC(strref line, int width, strref source_file); StatusCode Directive_DS(strref line); StatusCode Directive_ALIGN(strref line); StatusCode Directive_EVAL(strref line); StatusCode Directive_HEX(strref line); StatusCode Directive_ENUM_STRUCT(strref line, AssemblerDirective dir); // Assembler steps StatusCode GetAddressMode(strref line, bool flipXY, uint32_t &validModes, AddrMode &addrMode, int &len, strref &expression); StatusCode AddOpcode(strref line, int index, strref source_file); StatusCode BuildLine(strref line); StatusCode BuildSegment(); // Display error in stderr void PrintError(strref line, StatusCode error, strref file = strref()); // 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); // Change CPU void SetCPU(CPUIndex CPU); // Syntax bool Merlin() const { return syntax == SYNTAX_MERLIN; } bool KickAsm() const { return syntax == SYNTAX_KICKASM; } // constructor Asm() : opcode_table(opcodes_6502), opcode_count(num_opcodes_6502), num_instructions(0), cpu(CPU_6502), list_cpu(CPU_6502) { Cleanup(); localLabels.reserve(256); loadedData.reserve(16); lateEval.reserve(64); } }; void SymbolStackTable::PushSymbol(Label* symbol) { uint64_t key = symbol->label_name.fnv1a_64(symbol->pool_name.fnv1a_64()); SymbolStack** ppStack = InsertKey(key); // ppStack will exist but contains a pointer that may not exist if (!*ppStack) { *ppStack = new SymbolStack; } ValueOrString val; val.value = symbol->value; (*ppStack)->push_back(val); } StatusCode SymbolStackTable::PullSymbol(Label* symbol) { uint64_t key = symbol->label_name.fnv1a_64(symbol->pool_name.fnv1a_64()); SymbolStack** ppStack = GetValue(key); if (!ppStack || !(*ppStack)->size()) { return ERROR_PULL_WITHOUT_PUSH; } symbol->value = (**ppStack)[(*ppStack)->size() - 1].value; (*ppStack)->pop_back(); return STATUS_OK; } void SymbolStackTable::PushSymbol(StringSymbol* string) { uint64_t key = string->string_name.fnv1a_64(); SymbolStack** ppStack = InsertKey(key); // ppStack will exist but contains a pointer that may not exist if (!*ppStack) { *ppStack = new SymbolStack; } ValueOrString val; val.string = nullptr; if (string->string_value) { val.string = (char*)malloc(string->string_value.get_len() + 1); memcpy(val.string, string->string_value.get(), string->string_value.get_len()); val.string[string->string_value.get_len()] = 0; } (*ppStack)->push_back(val); } StatusCode SymbolStackTable::PullSymbol(StringSymbol* string) { uint64_t key = string->string_name.fnv1a_64(); SymbolStack** ppStack = GetValue(key); if (!ppStack || !(*ppStack)->size()) { return ERROR_PULL_WITHOUT_PUSH; } char* str = (**ppStack)[(*ppStack)->size() - 1].string; if (!str && string->string_value) { free(string->string_value.charstr()); string->string_value.invalidate(); } else { if (string->string_value.empty() || string->string_value.cap() < (strlen(str) + 1)) { if (string->string_value.charstr()) { free(string->string_value.charstr()); } string->string_value.set_overlay((char*)malloc(strlen(str) + 1), (strl_t)strlen(str) + 1); } string->string_value.copy(str); free(str); } (*ppStack)->pop_back(); return STATUS_OK; } SymbolStackTable::~SymbolStackTable() { for (size_t i = 0; i < size; ++i) { if (keys[i] && values[i]) { delete values[i]; values[i] = nullptr; keys[i] = 0; } } } UserFunction* UserFunctionMap::Get(strref name) { UserFunction** ret = GetValue(name.fnv1a_64()); if (ret) { return *ret; } return nullptr; } StatusCode UserFunctionMap::Add(strref name, strref params, strref expresion) { if (!name || !expresion) { return ERROR_INCOMPLETE_FUNCTION; } strl_t stringlen = name.get_len() + 1 + (params ? (params.get_len() + 1) : 0) + expresion.get_len() + 1; UserFunction *func = (UserFunction*)calloc(1, sizeof(UserFunction) + stringlen); char* strings = (char*)(func + 1); func->name = strings; memcpy(strings, name.get(), name.get_len()); strings[name.get_len()] = 0; strings += name.get_len() + 1; if (params) { func->params = strings; memcpy(strings, params.get(), params.get_len()); strings[params.get_len()] = 0; strings += params.get_len() + 1; } func->expression = strings; memcpy(strings, expresion.get(), expresion.get_len()); if (UserFunction** existing = GetValue(name.fnv1a_64())) { free(*existing); *existing = func; } else { InsertKeyValue(name.fnv1a_64(), func); } return STATUS_OK; } UserFunctionMap::~UserFunctionMap() { for (size_t i = 0; i < size; ++i) { if (keys[i] && values[i]) { free(values[i]); values[i] = nullptr; keys[i] = 0; } } } // Clean up work allocations void Asm::Cleanup() { for (std::vector<char*>::iterator i = loadedData.begin(); i != loadedData.end(); ++i) { if (char *data = *i) free(data); } for( std::vector<LateEval>::iterator i = lateEval.begin(); i != lateEval.end(); ++i ) { if( i->expression_mem ) { free( i->expression_mem ); i->expression_mem = nullptr; i->expression.clear(); } } map.clear(); labelPools.clear(); loadedData.clear(); labels.clear(); macros.clear(); allSections.clear(); lateEval.clear(); for (uint32_t i = 0; i < strings.count(); ++i) { StringSymbol &str = strings.getValue(i); if (str.string_value.cap()) free(str.string_value.charstr()); } strings.clear(); for (std::vector<ExtLabels>::iterator exti = externals.begin(); exti !=externals.end(); ++exti) exti->labels.clear(); externals.clear(); // this section is relocatable but is assigned address $1000 if exporting without directives SetSection(strref("default,code")); current_section = &allSections[0]; syntax = SYNTAX_SANE; default_org = 0x1000; scope_depth = 0; brace_depth = 0; conditional_depth = 0; conditional_nesting[0] = 0; conditional_consumed[0] = false; directive_scope_depth = 0; error_encountered = false; list_assembly = false; end_macro_directive = false; import_means_xref = false; accumulator_16bit = false; // default 65816 8 bit immediate mode index_reg_16bit = false; // other CPUs won't be affected. cycle_counter_level = 0; } int sortHashLookup(const void *A, const void *B) { const OPLookup *_A = (const OPLookup*)A; const OPLookup *_B = (const OPLookup*)B; return _A->op_hash > _B->op_hash ? 1 : -1; } int BuildInstructionTable(OPLookup *pInstr, struct mnem *opcodes, int count, const char **aliases, bool merlin) { // create an instruction table (mnemonic hash lookup) int numInstructions = 0; for (int i = 0; i < count; i++) { OPLookup &op = pInstr[numInstructions++]; op.op_hash = strref(opcodes[i].instr).fnv1a_lower(); op.index = (uint8_t)i; op.type = OT_MNEMONIC; } // add instruction aliases if (aliases) { while (*aliases) { strref orig(*aliases++); strref alias(*aliases++); for (int o=0; o<count; o++) { if (orig.same_str_case(opcodes[o].instr)) { OPLookup &op = pInstr[numInstructions++]; op.op_hash = alias.fnv1a_lower(); op.index = (uint8_t)o; op.type = OT_MNEMONIC; break; } } } } // add assembler directives for (int d = 0; d<nDirectiveNames; d++) { OPLookup &op_hash = pInstr[numInstructions++]; op_hash.op_hash = strref(aDirectiveNames[d].name).fnv1a_lower(); op_hash.index = (uint8_t)aDirectiveNames[d].directive; op_hash.type = OT_DIRECTIVE; } if (merlin) { for (int d = 0; d<nDirectiveNamesMerlin; d++) { OPLookup &op_hash = pInstr[numInstructions++]; op_hash.op_hash = strref(aDirectiveNamesMerlin[d].name).fnv1a_lower(); op_hash.index = (uint8_t)aDirectiveNamesMerlin[d].directive; op_hash.type = OT_DIRECTIVE; } } // sort table by hash for binary search lookup qsort(pInstr, numInstructions, sizeof(OPLookup), sortHashLookup); return numInstructions; } // Change the instruction set void Asm::SetCPU(CPUIndex CPU) { cpu = CPU; if (cpu > list_cpu) list_cpu = cpu; opcode_table = aCPUs[CPU].opcodes; opcode_count = aCPUs[CPU].num_opcodes; num_instructions = BuildInstructionTable(aInstructions, opcode_table, opcode_count, aCPUs[CPU].aliases, Merlin()); } // Read in text data (main source, include, etc.) char* Asm::LoadText(strref filename, size_t &size) { strown<512> file(filename); std::vector<strref>::iterator i = includePaths.begin(); for (;;) { if (FILE *f = fopen(file.c_str(), "rb")) { // rb is intended here since OS fseek(f, 0, SEEK_END); // eol conversion can do ugly things 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<strref>::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; } // Create a new section with a fixed address void Asm::SetSection(strref name, int address) { if (name) { for (std::vector<Section>::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); // don't compile over zero page and stack frame (may be bad assumption) if (address<0x200) { newSection.SetDummySection(true); } allSections.push_back(newSection); current_section = &allSections[allSections.size()-1]; } void Asm::SetSection(strref line) { if (allSections.size()==allSections.capacity()) { allSections.reserve(allSections.size()+16); } SectionType type = ST_UNDEFINED; if (line.get_first() == '.') { // SEG.U etc. ++line; switch (strref::tolower(line.get_first())) { case 'u': type = ST_BSS; break; case 'z': type = ST_ZEROPAGE; break; case 'd': type = ST_DATA; break; case 'c': type = ST_CODE; break; } } line.trim_whitespace(); int align = 1; strref name; while (strref arg = line.split_token_any_trim(",:")) { if (arg.get_first() == '$') { ++arg; align = (int)arg.ahextoui(); } else if (arg.is_number()) { align = (int)arg.atoi(); } else if (arg.get_first()=='"') { name = (arg+1).before_or_full('"'); } else if (!name) { name = arg; } else if (arg.same_str("code")) { type = ST_CODE; } else if (arg.same_str("data")) { type = ST_DATA; } else if (arg.same_str("bss")) { type = ST_BSS; } else if (arg.same_str("zp")||arg.same_str("dp")|| arg.same_str("zeropage")||arg.same_str("direct")) { type = ST_ZEROPAGE; } } if (type == ST_UNDEFINED) { if (name.find("code")>=0) { type = ST_CODE; } else if (name.find("data")>=0) { type = ST_DATA; } else if (name.find("bss") >= 0 || name.same_str("directpage_stack")) type = ST_BSS; else if (name.find("zp")>=0||name.find("zeropage")>=0||name.find("direct")>=0) { type = ST_ZEROPAGE; } else { type = ST_CODE; } } Section newSection(name); newSection.align_address = align; newSection.type = type; allSections.push_back(newSection); current_section = &allSections[allSections.size()-1]; } // Fixed address dummy section void Asm::DummySection(int address) { 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 = SectionId(); if (section) { current_section = &allSections[section-1]; } } // Iterate through the current group of sections and assign addresses if this section is fixed // This is to handle the special linking of Merlin where sections are brought together pre-export void Asm::AssignAddressToGroup() { Section &curr = CurrSection(); if (!curr.address_assigned) { return; } // Put in all the sections cared about into either the fixed sections or the relative sections std::vector<Section*> FixedExport; std::vector<Section*> RelativeExport; int seg = SectionId(); while (seg>=0) { Section &s = allSections[seg]; if (s.address_assigned && s.type != ST_ZEROPAGE && s.start_address >= curr.start_address) { bool inserted = false; for (std::vector<Section*>::iterator i = FixedExport.begin(); i!=FixedExport.end(); ++i) { if (s.start_address < (*i)->start_address) { FixedExport.insert(i, &s); inserted = true; break; } } if (!inserted) { FixedExport.push_back(&s); } } else if (!s.address_assigned && s.type != ST_ZEROPAGE) { RelativeExport.push_back(&s); s.export_append = curr.export_append; } seg = allSections[seg].next_group; } // in this case each block should be added individually in order of code / data / bss for (int type = ST_CODE; type <= ST_BSS; type++) { std::vector<Section*>::iterator i = RelativeExport.begin(); while (i!=RelativeExport.end()) { Section *pSec = *i; if (pSec->type == type) { int bytes = pSec->address - pSec->start_address; size_t insert_after = FixedExport.size()-1; for (size_t p = 0; p<insert_after; p++) { int end_prev = FixedExport[p]->address; int start_next = FixedExport[p+1]->start_address; int avail = start_next - end_prev; if (avail >= bytes) { int addr = end_prev; addr += pSec->align_address <= 1 ? 0 : (pSec->align_address - (addr % pSec->align_address)) % pSec->align_address; if ((addr + bytes) <= start_next) { insert_after = p; break; } } } int address = FixedExport[insert_after]->address; address += pSec->align_address <= 1 ? 0 : (pSec->align_address - (address % pSec->align_address)) % pSec->align_address; AssignAddressToSection(SectionId(*pSec), address); FixedExport.insert((FixedExport.begin() + insert_after + 1), pSec); i = RelativeExport.erase(i); } else { ++i; } } } } // list all export append names // for each valid export append name build a binary fixed address code // - find lowest and highest address // - alloc & 0 memory // - any matching relative sections gets linked in after // - go through all section that matches export_append in order and copy over memory uint8_t* Asm::BuildExport(strref append, int &file_size, int &addr) { int start_address = 0x7fffffff; int end_address = 0; bool has_relative_section = false; bool has_fixed_section = false; int first_link_section = -1; std::vector<Section*> FixedExport; // automatically merge sections with the same name and type if one is relative and other is fixed for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) { const Section §ion = allSections[section_id]; if (!section.IsMergedSection()&&!section.IsRelativeSection()) { for (size_t section_merge_id = 0; section_merge_id!=allSections.size(); ++section_merge_id) { const Section §ion_merge = allSections[section_merge_id]; if(!section_merge.IsMergedSection()&§ion_merge.IsRelativeSection() && section_merge.type == section.type && section.name.same_str_case(section_merge.name)) { MergeSections((int)section_id, (int)section_merge_id); } } } } // link any relocs to sections that are fixed for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) { const Section §ion = allSections[section_id]; if (section.address_assigned) { LinkRelocs((int)section_id, -1, section.start_address); } } // find address range while (!has_relative_section && !has_fixed_section) { int section_id = 0; for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) { if (((!append && !i->export_append) || append.same_str_case(i->export_append)) && i->type != ST_ZEROPAGE) { if (!i->IsMergedSection()) { if (i->IsRelativeSection()) { // prioritize code over data, local code over included code for initial binary segment if ((i->type == ST_CODE || i->type == ST_DATA) && i->first_group < 0 && (first_link_section < 0 || (i->type == ST_CODE && (allSections[first_link_section].type == ST_DATA || (!i->include_from && allSections[first_link_section].include_from))))) first_link_section = SectionId(*i); has_relative_section = true; } else if (i->start_address >= 0x100 && ( i->size() > 0 || i->addr_size() > 0 ) ) { has_fixed_section = true; bool inserted = false; for (std::vector<Section*>::iterator f = FixedExport.begin(); f != FixedExport.end(); ++f) { if ((*f)->start_address > i->start_address) { FixedExport.insert(f, &*i); inserted = true; break; } } if (!inserted) FixedExport.push_back(&*i); if (i->start_address < start_address) start_address = i->start_address; if ((i->start_address + (int)i->size()) > end_address) { end_address = i->start_address + (int)i->size(); } } } } section_id++; } if (!has_relative_section && !has_fixed_section) return nullptr; if (has_relative_section) { if (!has_fixed_section) { // there is not a fixed section so go through and assign addresses to all sections // starting with the first reasonable section start_address = default_org; if (first_link_section<0) { return nullptr; } while (first_link_section >= 0) { FixedExport.push_back(&allSections[first_link_section]); AssignAddressToSection(first_link_section, start_address); start_address = allSections[first_link_section].address; first_link_section = allSections[first_link_section].next_group; } } // First link code sections, then data sections, then BSS sections for (int sectype = ST_CODE; sectype <= ST_BSS; sectype++) { // there are fixed sections so fit all relative sections after or inbetween fixed sections in export group for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) { if (sectype == i->type && ((!append && !i->export_append) || append.same_str_case(i->export_append))) { int id = (int)(&*i - &allSections[0]); if (i->IsRelativeSection() && i->first_group < 0) { // try to fit this section in between existing sections if possible int insert_after = (int)FixedExport.size()-1; for (int f = 0; f < insert_after; f++) { int start_block = FixedExport[f]->address; int end_block = FixedExport[f + 1]->start_address; if ((end_block - start_block) >= (i->address - i->start_address)) { int addr_block = start_block; int sec = id; while (sec >= 0) { Section &s = allSections[sec]; addr_block += s.align_address <= 1 ? 0 : (s.align_address - (addr_block % s.align_address)) % s.align_address; addr_block += s.address - s.start_address; sec = s.next_group; } if (addr_block <= end_block) { insert_after = f; break; } } } int sec = id; start_address = FixedExport[insert_after]->address; while (sec >= 0) { insert_after++; if (insert_after<(int)FixedExport.size()) { FixedExport.insert(FixedExport.begin()+insert_after, &allSections[sec]); } else { FixedExport.push_back(&allSections[sec]); } AssignAddressToSection(sec, start_address); start_address = allSections[sec].address; sec = allSections[sec].next_group; } } } } } for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) { const Section §ion = allSections[section_id]; if (section.address_assigned) { LinkRelocs((int)section_id, -1, section.start_address); } } } } // get memory for output buffer start_address = FixedExport[0]->start_address; int last_data_export = (int)(FixedExport.size() - 1); while (last_data_export>0&&FixedExport[last_data_export]->type==ST_BSS) { last_data_export--; } end_address = FixedExport[last_data_export]->address; uint8_t *output = (uint8_t*)calloc(1, end_address - start_address); // copy over in order for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) { if (i->type == ST_REMOVED) { continue; } if (((!append && !i->export_append) || append.same_str_case(i->export_append)) && i->type != ST_ZEROPAGE) { if (i->size()>0) { memcpy(output+i->start_address-start_address, i->output, i->size()); } } } printf("Linker export + \"" STRREF_FMT "\" summary:\n", STRREF_ARG(append)); for (std::vector<Section*>::iterator f = FixedExport.begin(); f != FixedExport.end(); ++f) { if ((*f)->include_from) { printf("* $%04x-$%04x: " STRREF_FMT " (%d) included from " STRREF_FMT "\n", (*f)->start_address, (*f)->address, STRREF_ARG((*f)->name), (int)(*f - &allSections[0]), STRREF_ARG((*f)->include_from)); } else { printf("* $%04x-$%04x: " STRREF_FMT " (%d)\n", (*f)->start_address, (*f)->address, STRREF_ARG((*f)->name), (int)(*f - &allSections[0])); } } // return the result file_size = end_address - start_address; addr = start_address; return output; } // Collect all the export names int Asm::GetExportNames(strref *aNames, int maxNames) { int count = 0; for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) { if (!i->IsMergedSection()) { bool found = false; uint32_t hash = i->export_append.fnv1a_lower(); for (int n = 0; n < count; n++) { if (aNames[n].fnv1a_lower() == hash) { found = true; break; } } if (!found && count<maxNames) { aNames[count++] = i->export_append; } } } return count; } // Collect all unassigned ZP sections and link them StatusCode Asm::LinkZP() { uint8_t min_addr = 0xff, max_addr = 0x00; int num_addr = 0; bool has_assigned = false, has_unassigned = false; int first_unassigned = -1; // determine if any zeropage section has been asseigned for (std::vector<Section>::iterator s = allSections.begin(); s!=allSections.end(); ++s) { if (s->type==ST_ZEROPAGE&&!s->IsMergedSection()) { if (s->address_assigned) { has_assigned = true; if (s->start_address<(int)min_addr) { min_addr = (uint8_t)s->start_address; } else if ((int)s->address>max_addr) { max_addr = (uint8_t)s->address; } } else { has_unassigned = true; first_unassigned = first_unassigned>=0 ? first_unassigned : (int)(&*s-&allSections[0]); } num_addr += s->address-s->start_address; } } if (num_addr>0x100) { return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE; } // no unassigned zp section, nothing to fix if (!has_unassigned) { return STATUS_OK; } StatusCode status = STATUS_OK; if (!has_assigned) { // no section assigned => fit together at end int address = 0x100 - num_addr; for (std::vector<Section>::iterator s = allSections.begin(); status==STATUS_OK && s != allSections.end(); ++s) { if (s->type == ST_ZEROPAGE && !s->IsMergedSection()) { status = AssignAddressToSection((int)(&*s - &allSections[0]), address); address = s->address; } } } else { // find first fit neighbouring an address assigned zero page section for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) { if (s->type == ST_ZEROPAGE && !s->IsMergedSection() && !s->address_assigned) { int size = s->address - s->start_address; bool found = false; // find any assigned address section and try to place before or after for (std::vector<Section>::iterator sa = allSections.begin(); sa != allSections.end(); ++sa) { if (sa->type == ST_ZEROPAGE && !sa->IsMergedSection() && sa->address_assigned) { for (int e = 0; e < 2; ++e) { int start = e ? sa->start_address - size : sa->address; int align_size = s->align_address <= 1 ? 0 : (s->align_address - (start % s->align_address)) % s->align_address; start += align_size; int end = start + size; if (start >= 0 && end <= 0x100) { for (std::vector<Section>::iterator sc = allSections.begin(); !found && sc != allSections.end(); ++sc) { found = true; if (&*sa != &*sc && sc->type == ST_ZEROPAGE && !sc->IsMergedSection() && sc->address_assigned) { if (start <= sc->address && sc->start_address <= end) found = false; } } } if (found) { AssignAddressToSection((int)(&*s-&allSections[0]), start); } } } } if (!found) { return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE; } } } } return status; } // Apply labels assigned to addresses in a relative section a fixed address or as part of another section void Asm::LinkLabelsToAddress(int section_id, int section_new, 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 = section_new; if (pLabels->mapIndex>=0 && pLabels->mapIndex<(int)map.size()) { struct MapSymbol &msym = map[pLabels->mapIndex]; msym.value = pLabels->value; msym.section = (int16_t)section_new; } 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_new, int section_address) { for (std::vector<Section>::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; // only finalize the target value if fixed address if (section_new == -1 || allSections[section_new].address_assigned) { uint8_t *trg = trg_sect->output + output_offs + i->section_offset; int value = i->base_value + section_address; if (i->shift < 0) value >>= -i->shift; else if (i->shift) value <<= i->shift; for (int b = 0; b < i->bytes; b++) *trg++ = (uint8_t)(value >> (b * 8)); i = pList->erase(i); if (i!=pList->end()) { ++i; } } } } if (pList->empty()) { free(pList); s2.pRelocs = nullptr; } } } return STATUS_OK; } // Append one section to the end of another StatusCode Asm::AssignAddressToSection(int section_id, int address) { if (section_id<0||section_id>=(int)allSections.size()) { return ERROR_NOT_A_SECTION; } Section &s = allSections[section_id]; if (s.address_assigned) return ERROR_CANT_REASSIGN_FIXED_SECTION; // fix up the alignment of the address int align_size = s.align_address<=1 ? 0 : (s.align_address - (address%s.align_address))%s.align_address; address += align_size; s.start_address = address; s.address += address; s.address_assigned = true; LinkLabelsToAddress(section_id, -1, s.start_address); return LinkRelocs(section_id, -1, s.start_address); } // Link sections with a specific name at this point // Relative sections will just be appeneded to a grouping list // Fixed address sections will be merged together StatusCode Asm::LinkSections(strref name) { if (CurrSection().IsDummySection()) { return ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION; } int last_section_group = CurrSection().next_group; while (last_section_group > -1 && allSections[last_section_group].next_group > -1) last_section_group = allSections[last_section_group].next_group; for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) { if ((!name || i->name.same_str_case(name)) && i->IsRelativeSection() && !i->IsMergedSection()) { // it is ok to link other sections with the same name to this section if (&*i==&CurrSection()) { continue; } // Zero page sections can only be linked with zero page sections if (i->type != ST_ZEROPAGE || CurrSection().type == ST_ZEROPAGE) { i->export_append = CurrSection().export_append; if (!i->address_assigned) { if (i->first_group < 0) { int prev = last_section_group >= 0 ? last_section_group : SectionId(); int curr = (int)(&*i - &allSections[0]); allSections[prev].next_group = curr; i->first_group = CurrSection().first_group >= 0 ? CurrSection().first_group : SectionId(); last_section_group = curr; } } } else { return ERROR_CANT_LINK_ZP_AND_NON_ZP; } } } return STATUS_OK; } StatusCode Asm::MergeSections(int section_id, int section_merge) { if (section_id==section_merge||section_id<0||section_merge<0) { return STATUS_OK; } Section &s = allSections[section_id]; Section &m = allSections[section_merge]; // merging section needs to be relative to be appended if (!m.IsRelativeSection()) { return ERROR_CANT_APPEND_SECTION_TO_TARGET; } // if merging section is aligned and target section is not aligned to that or multiple of then can't merge if (m.align_address>1&&(!s.IsRelativeSection()||(s.align_address%m.align_address)!=0)) { return ERROR_CANT_APPEND_SECTION_TO_TARGET; } m.merged_size = m.address - m.start_address; // append the binary to the target.. int addr_start = s.address; int align = m.align_address <= 1 ? 0 : (m.align_address - (addr_start % m.align_address)) % m.align_address; if (m.size()) { if (s.CheckOutputCapacity(m.size() + align) == STATUS_OK) { for (int a = 0; a<align; a++) { s.AddByte(0); } s.AddBin(m.output, m.size()); } } else if (m.addr_size() && s.type != ST_BSS && s.type != ST_ZEROPAGE && !s.dummySection) { if (s.CheckOutputCapacity(m.address - m.start_address) == STATUS_OK) { for (int a = (m.start_address-align); a<m.address; a++) { s.AddByte(0); } } } else if (m.addr_size()) { s.AddAddress(align+m.addr_size()); } addr_start += align - s.start_address; // append info for result output m.merged_at = addr_start; m.merged_into = section_id; // move the relocs from the merge section to the keep section if (m.pRelocs) { if (!s.pRelocs) { s.pRelocs = new relocList; } if (s.pRelocs->capacity()<(s.pRelocs->size()+m.pRelocs->size())) { s.pRelocs->reserve(s.pRelocs->size()+m.pRelocs->size()); } for (relocList::iterator r = m.pRelocs->begin(); r!=m.pRelocs->end(); ++r) { struct Reloc rel = *r; rel.section_offset += addr_start; s.pRelocs->push_back(rel); } delete m.pRelocs; m.pRelocs = nullptr; } // go through all the relocs referring to merging section and replace for (std::vector<Section>::iterator i = allSections.begin(); i!=allSections.end(); ++i) { if (relocList *pReloc = i->pRelocs) { for (relocList::iterator r = pReloc->begin(); r!=pReloc->end(); ++r) { if (r->target_section == section_merge) { r->base_value += addr_start; r->target_section = section_id; } } } } if(!s.IsRelativeSection()) { LinkLabelsToAddress(section_merge, -1, m.start_address); } // go through all labels referencing merging section for (uint32_t i = 0; i<labels.count(); i++) { Label &lab = labels.getValue(i); if (lab.section == section_merge && lab.evaluated) { lab.value += addr_start; lab.section = section_id; } } // go through map symbols for (MapSymbolArray::iterator i = map.begin(); i!=map.end(); ++i) { if (i->section == section_merge) { i->value += addr_start; i->section = (int16_t)section_id; } } // go through all late evals referencing this section for (std::vector<LateEval>::iterator i = lateEval.begin(); i!=lateEval.end(); ++i) { if (i->section == section_merge) { i->section = (int16_t)section_id; if (i->target>=0) { i->target += addr_start; } i->address += addr_start; if (i->scope>=0) { i->scope += addr_start; } } } if (m.output) { free(m.output); m.output = nullptr; m.curr = nullptr; } // go through listing if (m.pListing) { if (!s.pListing) { s.pListing = new Listing; } if (s.pListing->capacity()<(m.pListing->size()+s.pListing->size())) { s.pListing->reserve((m.pListing->size()+s.pListing->size())); } for (Listing::iterator i = m.pListing->begin(); i!=m.pListing->end(); ++i) { ListLine l = *i; l.address += addr_start; s.pListing->push_back(l); } delete m.pListing; m.pListing = nullptr; } m.type = ST_REMOVED; return STATUS_OK; } // Go through sections and merge same name sections together StatusCode Asm::MergeSectionsByName(int first_section) { int first_code_seg = -1; StatusCode status = STATUS_OK; for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) { if (i->type != ST_REMOVED) { if (first_code_seg<0 && i->type==ST_CODE) first_code_seg = (int)(&*i-&allSections[0]); std::vector<Section>::iterator n = i; ++n; while (n != allSections.end()) { if (n->name.same_str_case(i->name) && n->type == i->type) { int sk = (int)(&*i - &allSections[0]); int sm = (int)(&*n - &allSections[0]); if (sm == first_section || (n->align_address > i->align_address)) { if (n->align_address<i->align_address) { n->align_address = i->align_address; } status = MergeSections(sm, sk); } else { status = MergeSections(sk, sm); } if (status!=STATUS_OK) { return status; } } ++n; } } } return STATUS_OK; } // Merge all sections in order of code, data, bss and make sure a specific section remains first #define MERGE_ORDER_CNT (ST_BSS - ST_CODE+1) StatusCode Asm::MergeAllSections(int first_section) { StatusCode status = STATUS_OK; // combine all sections by type first for (int t = ST_CODE; t<ST_ZEROPAGE && status == STATUS_OK; t++) { for (int i = 0; i<(int)allSections.size() && status == STATUS_OK; ++i) { if (allSections[i].type == t) { for (int j = i + 1; j<(int)allSections.size() && status == STATUS_OK; ++j) { if (allSections[j].type == t) { if (j == first_section || (t != ST_CODE && allSections[i].align_address<allSections[j].align_address)) { if (allSections[i].align_address>allSections[j].align_address) { allSections[i].align_address = allSections[j].align_address; } status = MergeSections(j, i); } else status = MergeSections(i, j); } } } } } // then combine by category except zero page int merge_order[MERGE_ORDER_CNT] = { -1 }; for (int t = ST_CODE; t <= ST_BSS; t++) { for (int i = 0; i<(int)allSections.size(); ++i) { if (allSections[i].type == t) { merge_order[t - ST_CODE] = i; break; } } } for (int n = 1; n < MERGE_ORDER_CNT; n++) { if (merge_order[n] == -1) { for (int m = n + 1; m < MERGE_ORDER_CNT; m++) merge_order[m - 1] = merge_order[m]; } } if (merge_order[0]==-1) { return ERROR_NOT_A_SECTION; } for (int o = 1; o < MERGE_ORDER_CNT; o++) { if (merge_order[o] != -1 && status == STATUS_OK) { if (allSections[merge_order[0]].align_address<allSections[merge_order[o]].align_address) { allSections[merge_order[0]].align_address = allSections[merge_order[o]].align_address; } status = MergeSections(merge_order[0], merge_order[o]); } } return status; } // Section based output capacity // Make sure there is room to assemble in StatusCode Section::CheckOutputCapacity(uint32_t addSize) { if (dummySection||type==ST_ZEROPAGE||type==ST_BSS) { return STATUS_OK; } 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; } if (uint8_t *new_output = (uint8_t*)malloc(newSize)) { memcpy(new_output, output, size()); curr = new_output + (curr - output); free(output); output = new_output; output_capacity = newSize; } else { return ERROR_OUT_OF_MEMORY; } } return STATUS_OK; } // Add one byte to a section void Section::AddByte(int b) { if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) { if (CheckOutputCapacity(1)==STATUS_OK) { *curr++ = (uint8_t)b; } } address++; } // Add a 16 bit word to a section void Section::AddWord(int w) { if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) { if (CheckOutputCapacity(2) == STATUS_OK) { *curr++ = (uint8_t)(w & 0xff); *curr++ = (uint8_t)(w >> 8); } } address += 2; } // Add a 24 bit word to a section void Section::AddTriple(int l) { if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) { if (CheckOutputCapacity(3) == STATUS_OK) { *curr++ = (uint8_t)(l & 0xff); *curr++ = (uint8_t)(l >> 8); *curr++ = (uint8_t)(l >> 16); } } address += 3; } // Add arbitrary length data to a section void Section::AddBin(const uint8_t *p, int size) { if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) { if (CheckOutputCapacity(size) == STATUS_OK) { memcpy(curr, p, size); curr += size; } } address += size; } // Add text data to a section void Section::AddText(strref line, strref text_prefix) { // https://en.wikipedia.org/wiki/PETSCII // ascii: no change // shifted: a-z => $41.. A-Z => $61.. // unshifted: a-z, A-Z => $41 if (CheckOutputCapacity((uint32_t)line.get_len()) == STATUS_OK) { if (!text_prefix || text_prefix.same_str("ascii")) { AddBin((const uint8_t*)line.get(), (int)line.get_len()); } else if (text_prefix.same_str("petscii")) { while (line) { char c = line[0]; AddByte((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : (c > 0x60 ? ' ' : line[0])); ++line; } } else if (text_prefix.same_str("petscii_shifted")) { while (line) { char c = line[0]; AddByte((c >= 'a' && c <= 'z') ? (c - 'a' + 0x61) : ((c >= 'A' && c <= 'Z') ? (c - 'A' + 0x61) : (c > 0x60 ? ' ' : line[0]))); ++line; } } } } void Section::AddIndexText(StringSymbol *strSym, strref text) { if (CheckOutputCapacity((uint32_t)text.get_len())==STATUS_OK) { const strref lookup = strSym->get(); while (text) { char c = text.pop_first(); AddByte(lookup.find(c)); } } } // Add a relocation marker to a section void Section::AddReloc(int base, int offset, int section, int8_t bytes, int8_t shift) { if (!pRelocs) { pRelocs = new relocList; } if (pRelocs->size()==pRelocs->capacity()) { pRelocs->reserve(pRelocs->size()+32); } pRelocs->push_back(Reloc(base, offset, section, bytes, shift)); } // Make sure there is room to assemble in StatusCode Asm::CheckOutputCapacity(uint32_t addSize) { return CurrSection().CheckOutputCapacity(addSize); } // // // SCOPE MANAGEMENT // // StatusCode Asm::EnterScope() { if (scope_depth>=(MAX_SCOPE_DEPTH-1)) { return ERROR_TOO_DEEP_SCOPE; } scope_address[++scope_depth] = CurrSection().GetPC(); return STATUS_OK; } StatusCode Asm::ExitScope() { StatusCode error = CheckLateEval(strref(), CurrSection().GetPC()); if( error >= FIRST_ERROR ) { return error; } error = FlushLocalLabels(scope_depth); if (error>=FIRST_ERROR) { return error; } --scope_depth; if (scope_depth<0) { return ERROR_UNBALANCED_SCOPE_CLOSURE; } return STATUS_OK; } // // // CONTEXT ISOLATION // // StatusCode Asm::PushContext(strref src_name, strref src_file, strref code_seg, int rept) { if (conditional_depth>=(MAX_CONDITIONAL_DEPTH-1)) { return ERROR_CONDITION_TOO_NESTED; } conditional_depth++; conditional_nesting[conditional_depth] = 0; conditional_consumed[conditional_depth] = false; contextStack.push(src_name, src_file, code_seg, rept); contextStack.curr().conditional_ctx = (int16_t)conditional_depth; if (scope_depth>=(MAX_SCOPE_DEPTH-1)) { return ERROR_TOO_DEEP_SCOPE; } else { scope_address[++scope_depth] = CurrSection().GetPC(); } return STATUS_OK; } StatusCode Asm::PopContext() { if (scope_depth) { StatusCode ret = ExitScope(); if (ret!=STATUS_OK) { return ret; } } if (!ConditionalAsm()||ConditionalConsumed()|| conditional_depth!=contextStack.curr().conditional_ctx) { return ERROR_UNTERMINATED_CONDITION; } conditional_depth = contextStack.curr().conditional_ctx-1; contextStack.pop(); return STATUS_OK; } // // // MACROS // // // add a custom macro StatusCode Asm::AddMacro(strref macro, strref source_name, strref source_file, strref &left) { // // Recommended macro syntax: // macro name(optional params) { actual macro } // // -endm option macro syntax: // macro name arg // actual macro // endmacro // // Merlin macro syntax: (TODO: ignore arguments and use ]1, ]2, etc.) // name mac arg1 arg2 // actual macro // [<<<]/[EOM] // strref name; bool params_first_line = false; if (Merlin()) { if (Label *pLastLabel = GetLabel(last_label)) { labels.remove((uint32_t)(pLastLabel - labels.getValues())); name = last_label; last_label.clear(); macro.skip_whitespace(); if (macro.get_first()==';'||macro.has_prefix(c_comment)) { macro.line(); } else { params_first_line = true; } } else { return ERROR_BAD_MACRO_FORMAT; } } else { name = macro.split_range(label_end_char_range); while (macro.get_first() == ' ' || macro.get_first() == '\t') { ++macro; } strref left_line = macro.get_line(); if (left_line.get_first() == ';' || left_line.has_prefix("//")) { left_line.clear(); } left_line.skip_whitespace(); left_line = left_line.before_or_full(';').before_or_full(c_comment); if (left_line && left_line[0]!='(' && left_line[0]!='{') { params_first_line = true; } } uint32_t hash = name.fnv1a(); uint32_t 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; if (Merlin()) { strref source = macro; while (strref next_line = macro.line()) { next_line = next_line.before_or_full(';'); next_line = next_line.before_or_full(c_comment); int term = next_line.find("<<<"); if (term<0) { term = next_line.find("EOM"); } if (term >= 0) { strl_t macro_len = strl_t(next_line.get() + term - source.get()); source = source.get_substr(0, macro_len); break; } } left = macro; pMacro->macro = source; source.skip_whitespace(); } else if (end_macro_directive) { int f = -1; const strref endm("endm"); for (;;) { f = macro.find(endm, f+1); if (f<0) { return ERROR_BAD_MACRO_FORMAT; } if (f == 0 || strref::is_ws(macro[f - 1]) || macro[f - 1] == '.') { if (f && macro[f - 1] == '.') { --f; } break; } } pMacro->macro = macro.get_substr(0, f); macro += f; macro.line(); left = macro; } else { 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); source.skip_whitespace(); left = source; } pMacro->source_name = source_name; pMacro->source_file = source_file; pMacro->params_first_line = params_first_line; return STATUS_OK; } // Compile in a macro StatusCode Asm::BuildMacro(Macro &m, strref arg_list) { strref macro_src = m.macro, params; if (m.params_first_line) { if (end_macro_directive||Merlin()) { params = macro_src.line(); } else { params = macro_src.before('{'); macro_src += params.get_len(); } } else { params = (macro_src[0]=='(' ? macro_src.scoped_block_skip() : strref()); } params.trim_whitespace(); arg_list.trim_whitespace(); if (Merlin()) { // need to include comment field because separator is ; if (contextStack.curr().read_source.is_substr(arg_list.get())) arg_list = (contextStack.curr().read_source + strl_t(arg_list.get()-contextStack.curr().read_source.get()) ).line(); arg_list = arg_list.before_or_full(c_comment).get_trimmed_ws(); strref arg = arg_list; strown<16> tag; int t_max = 16; int dSize = 0; for (int t=1; t<t_max; t++) { tag.sprintf("]%d", t); strref a = arg.split_token_trim(';'); if (!a) { t_max = t; break; } int count = macro_src.substr_case_count(tag.get_strref()); dSize += count * ((int)a.get_len() - (int)tag.get_len()); } int mac_size = (int)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); arg = arg_list; if (tag) { strref match("]*{0-9}"); strl_t pos = 0; while (strref tag_mac = macexp.find_wildcard(match, pos)) { bool success = false; strl_t offs = strl_t(tag_mac.get() - macexp.get()); if (!offs || !strref::is_valid_label(macexp[offs])) { int t = (int)(tag_mac + 1).atoi(); strref args = arg; if (t > 0) { for (int skip = 1; skip < t; skip++) args.split_token_trim(';'); strref a = args.split_token_trim(';'); macexp.exchange(offs, tag_mac.get_len(), a); pos += a.get_len(); success = true; } } if (!success) { return ERROR_MACRO_ARGUMENT; } } } PushContext(m.source_name, macexp.get_strref(), macexp.get_strref()); return STATUS_OK; } else { return ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION; } } else if (params) { if (arg_list[0]=='(') arg_list = arg_list.scoped_block_skip(); strref pchk = params; strref arg = arg_list; int dSize = 0; char token = arg_list.find(',')>=0 ? ',' : ' '; char token_macro = m.params_first_line && params.find(',') < 0 ? ' ' : ','; while (strref param = pchk.split_token_trim(token_macro)) { strref a = arg.split_token_trim(token); 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 = (int)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(token_macro)) { strref a = arg_list.split_token_trim(token); macexp.replace_bookend(param, a, macro_arg_bookend); } PushContext(m.source_name, macexp.get_strref(), macexp.get_strref()); return STATUS_OK; } else { return ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION; } } PushContext(m.source_name, m.source_file, macro_src); return STATUS_OK; } // // // STRUCTS AND ENUMS // // // Enums are Structs in disguise StatusCode Asm::BuildEnum(strref name, strref declaration) { uint32_t hash = name.fnv1a(); uint32_t 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 = (uint16_t)structMembers.size(); pEnum->numMembers = 0; pEnum->size = 0; // enums are 0 sized int value = 0; struct EvalContext etx; SetEvalCtxDefaults(etx); while (strref line = declaration.line()) { line = line.before_or_full(','); line.trim_whitespace(); strref member_name = line.split_token_trim('='); line = line.before_or_full(';').before_or_full(c_comment).get_trimmed_ws(); if (line) { StatusCode error = EvalExpression(line, etx, value); if (error==STATUS_NOT_READY||error==STATUS_XREF_DEPENDENT) { return ERROR_ENUM_CANT_BE_ASSEMBLED; } else if (error!=STATUS_OK) { return error; } } struct MemberOffset member; member.offset = (uint16_t)value; member.name = member_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) { uint32_t hash = name.fnv1a(); uint32_t 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 = (uint16_t)structMembers.size(); uint32_t byte_hash = struct_byte.fnv1a(); uint32_t word_hash = struct_word.fnv1a(); uint16_t size = 0; uint16_t member_count = 0; while (strref line = declaration.line()) { line.trim_whitespace(); strref type = line.split_label(); if (!type) { continue; } line.skip_whitespace(); uint32_t type_hash = type.fnv1a(); uint16_t 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 { uint32_t 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++; } { // add a trailing member of 0 bytes to access the size of the structure struct MemberOffset bytes_member; bytes_member.offset = size; bytes_member.name = "bytes"; bytes_member.name_hash = bytes_member.name.fnv1a(); bytes_member.sub_struct = strref(); structMembers.push_back(bytes_member); 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; uint16_t offset = 0; while (strref struct_seg = name.split_token('.')) { strref sub_struct = struct_seg; uint32_t seg_hash = struct_seg.fnv1a(); if (pStruct) { struct MemberOffset *member = &structMembers[pStruct->first_member]; bool found = false; for (int i = 0; i<pStruct->numMembers; 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) { uint32_t hash = sub_struct.fnv1a(); uint32_t 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; } // // // USER FUNCTION EVAL // // int Asm::EvalUserFunction(UserFunction* user, strref params, EvalContext& etx) { strref expression(user->expression); strref orig_param(user->params); strref paraiter = orig_param; strref in_params = params; int newSize = expression.get_len(); while (strref param = paraiter.split_token(',')) { strref replace = in_params.split_token(','); param.trim_whitespace(); replace.trim_whitespace(); if (param.get_len() < replace.get_len()) { int count = expression.substr_count(param); newSize += count * (replace.get_len() - param.get_len()); } } char* subst = (char*)malloc(newSize); strovl subststr(subst, newSize); subststr.copy(expression); while (strref param = orig_param.split_token(',')) { strref replace = params.split_token(','); param.trim_whitespace(); replace.trim_whitespace(); subststr.replace_bookend(param, replace, macro_arg_bookend); } int value = 0; etx.internalErr = EvalExpression(subststr.get_strref(), etx, value); if (etx.internalErr != STATUS_OK && etx.internalErr < FIRST_ERROR) { etx.internalErr = ERROR_FUNCTION_DID_NOT_RESOLVE; } free(subst); return value; } // // // EVAL FUNCTIONS // // bool Asm::EvalFunction(strref function, strref& expression, EvalContext& etx, int &value) { // all eval functions take a parenthesis with arguments if (expression.get_first() != '(') { return false; } strref expRet = expression; strref params = expRet.scoped_block_comment_skip(); params.trim_whitespace(); if (function.get_first() == '.') { ++function; } // look up user defined function if (UserFunction* user = userFunctions.Get(function)) { expression = expRet; value = EvalUserFunction(user, params, etx); return true; } // built-in function for (int i = 0; i < nEvalFuncs; ++i) { if (function.same_str(aEvalFunctions[i].name)) { switch (aEvalFunctions[i].function) { case EF_DEFINED: expression = expRet; value = GetLabel(params, true) != nullptr ? 1 : 0; return true; case EF_REFERENCED: expression = expRet; if (Label* label = GetLabel(params, true)) { value = label->referenced; return true; } return true; case EF_BLANK: expression = expRet; if (params.get_first() == '{') { params = params.scoped_block_comment_skip(); } params.trim_whitespace(); value = params.is_empty(); return true; case EF_CONST: expression = expRet; if (Label* label = GetLabel(params, true)) { value = label->constant ? 1 : 0; } return true; case EF_SIZEOF: { expression = expRet; uint32_t hash = params.fnv1a(); uint32_t index = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count()); value = 0; while (index < labelStructs.count() && labelStructs.getKey(index) == hash) { if (params.same_str_case(labelStructs.getValue(index).name)) { value = (labelStructs.getValues() + index)->size; break; } ++index; } return true; } case EF_SIN: expression = expRet; value = 0; // TODO: implement trigsin return true; } return false; } } return false; } // // // EXPRESSIONS AND LATE EVALUATION // // int Asm::ReptCnt() const { return contextStack.curr().repeat_total - contextStack.curr().repeat; } void Asm::SetEvalCtxDefaults(struct EvalContext &etx) { etx.pc = CurrSection().GetPC(); // current address at point of eval etx.scope_pc = scope_address[scope_depth]; // current scope open at point of eval etx.scope_end_pc = -1; // late scope closure after eval etx.scope_depth = scope_depth; // scope depth for eval (must match current for scope_end_pc to eval) etx.relative_section = -1; // return can be relative to this section etx.file_ref = -1; // can access private label from this file or -1 etx.rept_cnt = contextStack.empty() ? 0 : ReptCnt(); // current repeat counter } // Get a single token from a merlin expression EvalOperator Asm::RPNToken_Merlin(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, int16_t §ion, int &value) { char c = expression.get_first(); switch (c) { case '$': ++expression; value = (int)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 = int16_t(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 = (int)expression.abinarytoui_skip(); return EVOP_VAL; } if (etx.scope_end_pc<0||scope_depth!=etx.scope_depth) return EVOP_NRY; ++expression; value = etx.scope_end_pc; section = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL; case '|': case '.': ++expression; return EVOP_OR; // MERLIN: . is or, | is not used case '^': if (prev_op==EVOP_VAL||prev_op==EVOP_RPR) { ++expression; return EVOP_EOR; } ++expression; return EVOP_BAB; 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 '?': return EVOP_STP; } 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 = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL; } else if (expression.match_chars_str("0-9", "!a-zA-Z_")) { 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 = 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 && label.same_str("rept")) { value = etx.rept_cnt; return EVOP_VAL; } if (!pLabel||!pLabel->evaluated) { return EVOP_NRY; } // this label could not be found (yet) value = pLabel->value; section = int16_t(pLabel->section); return EVOP_VAL; } return EVOP_ERR; } // Get a single token from most non-apple II assemblers EvalOperator Asm::RPNToken(strref &exp, EvalContext &etx, EvalOperator prev_op, int16_t §ion, int &value, strref &subexp) { char c = exp.get_first(); switch (c) { case '$': ++exp; value = (int)exp.ahextoui_skip(); return EVOP_VAL; case '-': ++exp; return EVOP_SUB; case '+': ++exp; return EVOP_ADD; case '*': ++exp; // asterisk means both multiply and current PC, disambiguate! if (exp[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 = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL; case '/': ++exp; return EVOP_DIV; case '=': if (exp[1] == '=') { exp += 2; return EVOP_EQU; } return EVOP_STP; case '>': if (exp.get_len() >= 2 && exp[1] == '>') { exp += 2; return EVOP_SHR; } if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++exp; if (exp[0] == '=') { ++exp; return EVOP_GTE; } return EVOP_GT; } ++exp; return EVOP_HIB; case '<': if (exp.get_len() >= 2 && exp[1] == '<') { exp += 2; return EVOP_SHL; } if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++exp; if (exp[0] == '=') { ++exp; return EVOP_LTE; } return EVOP_LT; } ++exp; return EVOP_LOB; case '%': // % means both binary and scope closure, disambiguate! if (exp[1] == '0' || exp[1] == '1') { ++exp; value = (int)exp.abinarytoui_skip(); return EVOP_VAL; } if (etx.scope_end_pc<0 || scope_depth != etx.scope_depth) return EVOP_NRY; ++exp; value = etx.scope_end_pc; section = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL; case '|': ++exp; return EVOP_OR; case '^': if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++exp; return EVOP_EOR; } ++exp; return EVOP_BAB; case '&': ++exp; return EVOP_AND; case '~': ++exp; return EVOP_NOT; case '(': if (prev_op != EVOP_VAL) { ++exp; return EVOP_LPR; } return EVOP_STP; case ')': ++exp; return EVOP_RPR; case ',': case '?': return EVOP_STP; case '\'': if( exp[ 2 ] == '\'' ) { value = exp[ 1 ]; exp += 3; return EVOP_VAL; } return EVOP_STP; } // ! by itself is current scope, !+label char is a local label if (c == '!' && !(exp + 1).len_label()) { if (etx.scope_pc < 0) return EVOP_NRY; ++exp; value = etx.scope_pc; section = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL; } else if (exp.match_chars_str("0-9", "!a-zA-Z_")) { if (prev_op == EVOP_VAL) return EVOP_STP; // value followed by value doesn't make sense, stop value = exp.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 = exp[0]; int start_pos = (e0 == ':' || e0 == '!' || e0 == '.') ? 1 : 0; strref label = exp.split_range_trim(label_end_char_range, start_pos); Label *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 && label.same_str("rept")) { value = etx.rept_cnt; return EVOP_VAL; } if (!pLabel) { if (StringSymbol *pStr = GetString(label)) { subexp = pStr->get(); return EVOP_EXP; } } if (!pLabel) { if (EvalFunction(label, exp, etx, value)) { return EVOP_VAL; } } if (!pLabel || !pLabel->evaluated) return EVOP_NRY; // this label could not be found (yet) value = pLabel->value; section = int16_t(pLabel->section); return pLabel->reference ? EVOP_XRF : EVOP_VAL; } return EVOP_ERR; } // // 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 // determine if a scalar can be a shift static int mul_as_shift(int scalar) { int shift = 0; while (scalar > 1 && (scalar & 1) == 0) { shift++; scalar >>= 1; } return scalar == 1 ? shift : 0; } #define MAX_EXPR_STACK 2 StatusCode Asm::EvalExpression(strref expression, EvalContext &etx, int &result) { int numValues = 0; int numOps = 0; strref expression_stack[MAX_EXPR_STACK]; int exp_sp = 0; char ops[MAX_EVAL_OPER]; // RPN expression int values[MAX_EVAL_VALUES]; // RPN values (in order of RPN EVOP_VAL operations) int16_t section_ids[MAX_EVAL_SECTIONS]; // local index of each referenced section int16_t section_val[MAX_EVAL_VALUES] = { 0 }; // each value can be assigned to one section, or -1 if fixed int16_t num_sections = 0; // number of sections in section_ids (normally 0 or 1, can be up to MAX_EVAL_SECTIONS) bool xrefd = false; // don't allow too deep recursion of this function, this should be rare as it takes a user function or variadic macro to recurse. if (etx.recursion > 3) { return ERROR_EXPRESSION_RECURSION; } etx.recursion++; // increment recursion of EvalExpression body 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 || exp_sp) { int value = 0; int16_t section = -1, index_section = -1; EvalOperator op = EVOP_NONE; strref subexp; if (!expression && exp_sp) { expression = expression_stack[--exp_sp]; op = EVOP_RPR; } else if (Merlin()) { op = RPNToken_Merlin(expression, etx, prev_op, section, value); } else { op = RPNToken(expression, etx, prev_op, section, value, subexp); } if (op == EVOP_ERR) { etx.recursion--; return ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION; } else if (op==EVOP_NRY) { etx.recursion--; return STATUS_NOT_READY; } else if (op == EVOP_EXP) { if (exp_sp>=MAX_EXPR_STACK) { etx.recursion--; return ERROR_TOO_MANY_VALUES_IN_EXPRESSION; } expression_stack[exp_sp++] = expression; expression = subexp; op = EVOP_LPR; } else if (op == EVOP_XRF) { xrefd = true; op = EVOP_VAL; } if (section >= 0) { for (int s = 0; s<num_sections && index_section<0; s++) { if (section_ids[s]==section) { index_section = (int16_t)s; } } if (index_section<0) { if (num_sections<=MAX_EVAL_SECTIONS) { section_ids[index_section = num_sections++] = section; } else { etx.recursion--; return STATUS_NOT_READY; } } } // this is the body of the shunting yard algorithm if (op == EVOP_VAL) { section_val[numValues] = index_section; // only value operators can be section specific values[numValues++] = value; ops[numOps++] = (char)op; } else if (op == EVOP_LPR) { op_stack[sp++] = (char)op; } else if (op == EVOP_RPR) { while (sp && op_stack[sp-1]!=EVOP_LPR) { sp--; ops[numOps++] = op_stack[sp]; } // check that there actually was a left parenthesis if (!sp||op_stack[sp-1]!=EVOP_LPR) { etx.recursion--; return ERROR_UNBALANCED_RIGHT_PARENTHESIS; } sp--; // skip open paren } else if (op == EVOP_STP) { break; } else { bool skip = false; if ((prev_op >= EVOP_EQU && prev_op <= EVOP_GTE) || (prev_op==EVOP_HIB || prev_op==EVOP_LOB)) { if (op==EVOP_SUB) { op = EVOP_NEG; } else if (op==EVOP_ADD) { skip = true; } } if (op==EVOP_SUB && sp && prev_op==EVOP_SUB) { sp--; } else { while (sp && !skip) { EvalOperator p = (EvalOperator)op_stack[sp-1]; if (p==EVOP_LPR||op>p) { break; } ops[numOps++] = (char)p; sp--; } op_stack[sp++] = (char)op; } } // check for out of bounds or unexpected input if (numValues==MAX_EVAL_VALUES) { etx.recursion--; return ERROR_TOO_MANY_VALUES_IN_EXPRESSION; } else if (numOps==MAX_EVAL_OPER||sp==MAX_EVAL_OPER) { etx.recursion--; return ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION; } prev_op = op; expression.skip_whitespace(); } while (sp) { sp--; ops[numOps++] = op_stack[sp]; } } etx.recursion--; // recursion only occurs in loop above // Check if dependent on XREF'd symbol if (xrefd) { return STATUS_XREF_DEPENDENT; } // 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 prev_val = values[0]; int shift_bits = 0; // special case for relative reference to low byte / high byte int16_t section_counts[MAX_EVAL_SECTIONS][MAX_EVAL_VALUES] = { 0 }; for (int o = 0; o<numOps; o++) { EvalOperator op = (EvalOperator)ops[o]; shift_bits = 0; prev_val = ri ? values[ri-1] : prev_val; if (op!=EVOP_VAL && op!=EVOP_LOB && op!=EVOP_HIB && op!=EVOP_BAB && op!=EVOP_SUB && op!=EVOP_NOT && ri<2) { break; // ignore suffix operations that are lacking values } switch (op) { case EVOP_VAL: // value for (int i = 0; i<num_sections; i++) { section_counts[i][ri] = i==section_val[ri] ? 1 : 0; } values[ri++] = values[valIdx++]; break; case EVOP_EQU: // == ri--; values[ri - 1] = values[ri - 1] == values[ri]; break; case EVOP_GT: // > ri--; values[ri - 1] = values[ri - 1] > values[ri]; break; case EVOP_LT: // < ri--; values[ri - 1] = values[ri - 1] < values[ri]; break; case EVOP_GTE: // >= ri--; values[ri - 1] = values[ri - 1] >= values[ri]; break; case EVOP_LTE: // >= ri--; values[ri - 1] = values[ri - 1] <= values[ri]; break; case EVOP_ADD: // + ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] += section_counts[i][ri]; } values[ri-1] += values[ri]; break; case EVOP_SUB: // - if (ri==1) { values[ri-1] = -values[ri-1]; } else if (ri>1) { ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] -= section_counts[i][ri]; } values[ri-1] -= values[ri]; } break; case EVOP_NEG: if (ri>=1) { values[ri-1] = -values[ri-1]; } break; case EVOP_MUL: // * ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } shift_bits = mul_as_shift(values[ri]); prev_val = values[ri - 1]; values[ri-1] *= values[ri]; break; case EVOP_DIV: // / ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } shift_bits = -mul_as_shift(values[ri]); prev_val = values[ri - 1]; values[ri - 1] /= values[ri]; break; case EVOP_AND: // & ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } values[ri-1] &= values[ri]; break; case EVOP_OR: // | ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } values[ri-1] |= values[ri]; break; case EVOP_EOR: // ^ ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } values[ri-1] ^= values[ri]; break; case EVOP_SHL: // << ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } shift_bits = values[ri]; prev_val = values[ri - 1]; values[ri - 1] <<= values[ri]; break; case EVOP_SHR: // >> ri--; for (int i = 0; i<num_sections; i++) { section_counts[i][ri-1] |= section_counts[i][ri]; } shift_bits = -values[ri]; prev_val = values[ri - 1]; values[ri - 1] >>= values[ri]; break; case EVOP_NOT: if (ri) { values[ri - 1] = ~values[ri - 1]; } break; case EVOP_LOB: // low byte if (ri) { values[ri-1] &= 0xff; } break; case EVOP_HIB: if (ri) { shift_bits = -8; values[ri - 1] = values[ri - 1] >> 8; } break; case EVOP_BAB: if (ri) { shift_bits = -16; values[ri - 1] = (values[ri - 1] >> 16); } break; default: return ERROR_EXPRESSION_OPERATION; break; } if (shift_bits==0&&ri) { prev_val = values[ri-1]; } } 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<num_sections; i++) { if (section_counts[i][0]) { if (section_counts[i][0]!=1||section_index>=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 = prev_val; lastEvalShift = (int8_t)shift_bits; return STATUS_RELATIVE_SECTION; } } return STATUS_OK; } // if labels referenced in the expression have been evaluated absolutely // create a new expression with those labels replaced by their value. char* Asm::PartialEval( strref expression ) { struct EvalContext etx; SetEvalCtxDefaults( etx ); strown< 1024 > partial; strref input = expression; EvalOperator prev_op = EVOP_NONE; bool partial_solved = false; while( expression ) { int value = 0; int16_t section = -1, index_section = -1; EvalOperator op = EVOP_NONE; strref subexp; while( expression.get_len() && strref::is_ws( expression.get_first() ) ) { partial.append( expression.get_first() ); ++expression; } if( expression ) { char f = expression.get_first(); if( strref::is_number( f ) ) { strl_t l = expression.len_alphanumeric(); partial.append( strref( expression.get(), l ) ); expression += l; } else if( f=='.' || f == '_' || strref::is_alphabetic( f ) ) { strref label = expression.split_range( label_end_char_range ); Label *pLabel = GetLabel( label, etx.file_ref ); if (pLabel && pLabel->evaluated&&!pLabel->external && pLabel->section<0) { partial.sprintf_append( "$%x ", pLabel->value ); // insert extra whitespace for separation partial_solved = true; } else { partial.append( label ); } } else { partial.append( expression.get_first() ); ++expression; } } } if( partial_solved ) { //printf( "PARTIAL EXPRESSION SOLVED:\n\t" STRREF_FMT "\n\t" STRREF_FMT "\n", STRREF_ARG( input ), STRREF_ARG( partial.get_strref() ) ); return _strdup( partial.c_str() ); } return nullptr; } // 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; char* partial = PartialEval( expression ); le.address = pc; le.scope = scope_pc; le.scope_depth = scope_depth; le.target = target; le.section = (int16_t)(&CurrSection() - &allSections[0]); le.rept = contextStack.curr().repeat_total - contextStack.curr().repeat; le.file_ref = -1; // current or xdef'd le.label.clear(); le.expression = partial ? strref( partial ) : expression; le.expression_mem = partial; 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; char* partial = PartialEval( expression ); le.address = pc; le.scope = scope_pc; le.scope_depth = scope_depth; le.target = -1; le.label = label; le.section = (int16_t)(&CurrSection() - &allSections[0]); le.rept = contextStack.curr().repeat_total - contextStack.curr().repeat; le.file_ref = -1; // current or xdef'd le.expression = partial ? strref( partial ) : expression; le.expression_mem = partial; 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 print_missing_reference_errors) { 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; while (evaluated_label) { evaluated_label = false; std::vector<LateEval>::iterator i = lateEval.begin(); 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; l<num_new_labels && !check; l++) check = i->expression.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, i->rept); etx.scope_depth = i->scope_depth; 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) { } bool resolved = true; switch (i->type) { case LateEval::LET_BYTE: if (ret==STATUS_RELATIVE_SECTION) { if (i->section<0) { resolved = false; } else { allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 1, lastEvalShift); 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, 2, lastEvalShift); 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_ABS_L_REF: if (ret==STATUS_RELATIVE_SECTION) { if (i->section<0) { resolved = false; } else { allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 3, lastEvalShift); value = 0; } } if ((trg+2)>=allSections[sec].size()) { return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; } allSections[sec].SetTriple(trg, value); break; case LateEval::LET_ABS_4_REF: if (ret==STATUS_RELATIVE_SECTION) { if (i->section<0) { resolved = false; } else { allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 4, lastEvalShift); value = 0; } } if ((trg+3)>=allSections[sec].size()) { return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; } allSections[sec].SetQuad(trg, value); break; case LateEval::LET_BRANCH: value -= i->address+1; if (value<-128 || value>127) { i = lateEval.erase(i); 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_BRANCH_16: value -= i->address+2; if (trg>=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; label->section = ret==STATUS_RELATIVE_SECTION ? i->section : -1; if (num_new_labels<MAX_LABELS_EVAL_ALL) { new_labels[num_new_labels++] = label->label_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 { if (print_missing_reference_errors && ret!=STATUS_XREF_DEPENDENT) { PrintError(i->expression, ret, i->source_file); error_encountered = true; } ++i; } } else { ++i; } } all = false; added_label.clear(); } return STATUS_OK; } // // // LABELS // // // Get a label record if it exists Label *Asm::GetLabel(strref label, bool reference_check) { uint32_t label_hash = label.fnv1a(); uint32_t 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)) { Label *label = labels.getValues() + index; if (!reference_check) { label->referenced = true; } return label; } 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]; uint32_t label_hash = label.fnv1a(); uint32_t 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)) { Label *label = labs.labels.getValues()+index; label->referenced = true; return label; } index++; } } return GetLabel(label); } // If exporting labels, append this label to the list void Asm::LabelAdded(Label *pLabel, bool local) { if (pLabel && pLabel->evaluated) { if (map.size()==map.capacity()) { map.reserve(map.size()+256); } MapSymbol sym; sym.name = pLabel->label_name; sym.section = (int16_t)(pLabel->section); sym.value = pLabel->value; sym.local = local; pLabel->mapIndex = pLabel->evaluated ? -1 : (int)map.size(); map.push_back(sym); } } // Add a label entry Label* Asm::AddLabel(uint32_t hash) { uint32_t 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 StatusCode Asm::FlushLocalLabels(int scope_exit) { StatusCode status = STATUS_OK; // iterate from end of local label records and early out if the label scope is lower than the current. std::vector<LocalLabelRecord>::iterator i = localLabels.end(); while (i!=localLabels.begin()) { --i; if (i->scope_depth<scope_depth) { break; } strref label = i->label; StatusCode this_status = CheckLateEval(label); if (this_status>FIRST_ERROR) { status = this_status; } if (!i->scope_reserve || i->scope_depth<=scope_exit) { uint32_t index = FindLabelIndex(label.fnv1a(), labels.getKeys(), labels.count()); while (index<labels.count()) { if (label.same_str_case(labels.getValue(index).label_name)) { labels.remove(index); break; } ++index; } i = localLabels.erase(i); } } return status; } // Get a label pool by name LabelPool* Asm::GetLabelPool(strref pool_name) { uint32_t pool_hash = pool_name.fnv1a(); uint32_t 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; } // Add a label pool StatusCode Asm::AddLabelPool(strref name, strref args) { uint32_t pool_hash = name.fnv1a(); uint32_t ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count()); uint32_t 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; uint32_t aRng[256]; struct EvalContext etx; SetEvalCtxDefaults(etx); 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++] = (uint32_t)addr0; aRng[ranges++] = (uint32_t)addr1; num32 += (addr1-addr0+15)>>4; if (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 = (int16_t)(ranges>>1); pool.depth = 0; pool.start = aRng[0]; pool.end = aRng[1]; labelPools.insert(ins, pool_hash); LabelPool &poolValue = labelPools.getValue(ins); poolValue = pool; return STATUS_OK; } StatusCode Asm::AssignPoolLabel(LabelPool &pool, strref label) { if (pool_subpool.is_prefix_word(label)) { // declaring a pool within another pool? label += pool_subpool.get_len(); label.skip_whitespace(); strref size = label; label = size.split_label(); if (strref::is_number(size.get_first())) { uint32_t bytes = (uint32_t)size.atoi(); if (!bytes) { return ERROR_POOL_RANGE_EXPRESSION_EVAL; } if (!GetLabelPool(label)) { uint32_t addr; StatusCode error = pool.Reserve(bytes, addr, (uint16_t)brace_depth); if( error == STATUS_OK ) { // permanently remove this chunk from the parent pool pool.end = addr; pool.depth = 0; uint32_t pool_hash = label.fnv1a(); uint32_t ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count()); labelPools.insert(ins, pool_hash); LabelPool &subPool = labelPools.getValue(ins); subPool.pool_name = label; subPool.numRanges = 1; subPool.depth = 0; subPool.start = addr; subPool.end = addr+bytes; } return error; } else { return ERROR_LABEL_POOL_REDECLARATION; } } return ERROR_POOL_RANGE_EXPRESSION_EVAL; } strref type = label; uint32_t bytes = 1; int sz = label.find_at( '.', 1 ); if (sz > 0) { label = type.split( sz ); ++type; if (strref::is_number(type.get_first())) { bytes = (uint32_t)type.atoi(); } else { switch (strref::tolower(type.get_first())) { case 'l': bytes = 4; break; case 't': bytes = 3; break; case 'd': case 'w': bytes = 2; break; } } } if (GetLabel(label)) { return ERROR_POOL_LABEL_ALREADY_DEFINED; } uint32_t addr; StatusCode error = pool.Reserve(bytes, addr, (uint16_t)brace_depth); if (error!=STATUS_OK) { return error; } Label *pLabel = AddLabel(label.fnv1a()); pLabel->label_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; pLabel->reference = false; pLabel->referenced = false; bool local = false; if (label[ 0 ] == '.' || label[ 0 ] == '@' || label[ 0 ] == '!' || label[ 0 ] == ':' || label.get_last() == '$') { local = true; MarkLabelLocal( label, true ); } LabelAdded(pLabel, local); return error; } // Request a label from a pool StatusCode sLabelPool::Reserve(uint32_t numBytes, uint32_t &ret_addr, uint16_t scope) { if (numBytes>(end-start)||depth==MAX_SCOPE_DEPTH) { return ERROR_OUT_OF_LABELS_IN_POOL; } if (!depth||scope!=scopeUsed[depth-1][1]) { scopeUsed[depth][0] = end; scopeUsed[depth][1] = scope; ++depth; } end -= numBytes; ret_addr = end; return STATUS_OK; } // Release a label from a pool (at scope closure) void sLabelPool::ExitScope(uint16_t scope) { if (depth && scopeUsed[depth-1][1]==scope) { end = scopeUsed[--depth][0]; } } // Check if a label is marked as an xdef bool Asm::MatchXDEF(strref label) { uint32_t hash = label.fnv1a(); uint32_t 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 (<label> = <expression>) StatusCode Asm::AssignLabel(strref label, strref expression, bool make_constant) { expression.trim_whitespace(); int val = 0; struct EvalContext etx; SetEvalCtxDefaults(etx); StatusCode status = EvalExpression(expression, etx, val); if (status!=STATUS_NOT_READY && status!=STATUS_OK && status!=STATUS_RELATIVE_SECTION) { return status; } Label *pLabel = GetLabel(label); if (pLabel) { if (pLabel->constant && pLabel->evaluated && val!=pLabel->value) { return (status==STATUS_NOT_READY) ? STATUS_OK : ERROR_MODIFYING_CONST_LABEL; } } else { pLabel = AddLabel(label.fnv1a()); pLabel->referenced = false; } pLabel->label_name = label; pLabel->pool_name.clear(); pLabel->evaluated = status==STATUS_OK || status == STATUS_RELATIVE_SECTION; pLabel->section = status == STATUS_RELATIVE_SECTION ? lastEvalSection : -1; // assigned labels are section-less pLabel->value = val; pLabel->mapIndex = -1; pLabel->pc_relative = false; pLabel->constant = make_constant; pLabel->external = MatchXDEF(label); pLabel->reference = false; bool local = label[0]=='.' || label[0]=='@' || label[0]=='!' || label[0]==':' || label.get_last()=='$'; if (!pLabel->evaluated) { AddLateEval(label, CurrSection().GetPC(), scope_address[scope_depth], expression, LateEval::LET_LABEL); } else { if (local) { MarkLabelLocal(label); } LabelAdded(pLabel, local); return CheckLateEval(label); } return STATUS_OK; } // Adding a fixed address label StatusCode Asm::AddressLabel(strref label) { StatusCode status = STATUS_OK; Label *pLabel = GetLabel(label); bool constLabel = false; if (!pLabel) { pLabel = AddLabel(label.fnv1a()); pLabel->referenced = false; // if this label already exists but is changed then it may already have been referenced } else if (pLabel->constant && pLabel->value!=CurrSection().GetPC()) { return ERROR_MODIFYING_CONST_LABEL; } else { constLabel = pLabel->constant; } pLabel->label_name = label; pLabel->pool_name.clear(); pLabel->section = CurrSection().IsRelativeSection() ? SectionId() : -1; // address labels are based on section pLabel->value = CurrSection().GetPC(); pLabel->evaluated = true; pLabel->pc_relative = true; pLabel->external = MatchXDEF(label); pLabel->reference = false; pLabel->constant = constLabel; last_label = label; bool local = label[0]=='.' || label[0]=='@' || label[0]=='!' || label[0]==':' || label.get_last()=='$'; if (directive_scope_depth > 0) { local = true; } LabelAdded(pLabel, local); // TODO: in named scopes the label can still be referenced outside the scope directive if (local) { MarkLabelLocal(label); } status = CheckLateEval(label); if (!local && label[0]!=']') { // MERLIN: Variable label does not invalidate local labels StatusCode this_status = FlushLocalLabels(); if (status<FIRST_ERROR && this_status>=FIRST_ERROR) { status = this_status; } } return status; } // include symbols listed from a .sym file or all if no listing StatusCode Asm::IncludeSymbols(strref line) { strref symlist = line.before('"').get_trimmed_ws(); line = line.between('"', '"'); size_t size; if (char *buffer = LoadText(line, size)) { strref symfile(buffer, strl_t(size)); while (symfile) { symfile.skip_whitespace(); strref symstart = symfile; if (strref symline = symfile.line()) { int scope_start = symline.find('{'); if (scope_start != 0) { strref symdef = symline.get_substr(0, scope_start); symdef.clip_trailing_whitespace(); strref symtype = symdef.split_token(' '); strref label = symdef.split_token_trim('='); bool constant = symtype.same_str(".const"); // first word is either .label or .const if (symlist) { strref symchk = symlist; while (strref symwant = symchk.split_token_trim(',')) { if (symwant.same_str_case(label)) { AssignLabel(label, symdef, constant); break; } } } else AssignLabel(label, symdef, constant); } if (scope_start >= 0) { symfile = symstart + scope_start; symfile.scoped_block_skip(); } } } loadedData.push_back(buffer); } else return ERROR_COULD_NOT_INCLUDE_FILE; return STATUS_OK; } // Get a string record if it exists StringSymbol *Asm::GetString(strref string_name) { uint32_t string_hash = string_name.fnv1a(); uint32_t index = FindLabelIndex(string_hash, strings.getKeys(), strings.count()); while (index < strings.count() && string_hash == strings.getKey(index)) { if (string_name.same_str(strings.getValue(index).string_name)) return strings.getValues() + index; index++; } return nullptr; } // Add or modify a string record StringSymbol *Asm::AddString(strref string_name, strref string_value) { StringSymbol *pStr = GetString(string_name); if (pStr==nullptr) { uint32_t string_hash = string_name.fnv1a(); uint32_t index = FindLabelIndex(string_hash, strings.getKeys(), strings.count()); strings.insert(index, string_hash); pStr = strings.getValues() + index; pStr->string_name = string_name; pStr->string_value.invalidate(); pStr->string_value.clear(); } if (pStr->string_value.cap()) { free(pStr->string_value.charstr()); pStr->string_value.invalidate(); pStr->string_value.clear(); } pStr->string_const = string_value; return pStr; } // append a string to another string StatusCode sStringSymbols::Append(strref append) { if (!append) return STATUS_OK; strl_t add_len = append.get_len(); if (!string_value.cap()) { strl_t new_len = (add_len + 0xff)&(~(strl_t)0xff); char *buf = (char*)malloc(new_len); if (!buf) return ERROR_OUT_OF_MEMORY; string_value.set_overlay(buf, new_len); string_value.copy(string_const); } else if (string_value.cap() < (string_value.get_len() + add_len)) { strl_t new_len = (string_value.get_len() + add_len + 0xff)&(~(strl_t)0xff); char *buf = (char*)malloc(new_len); if (!buf) return ERROR_OUT_OF_MEMORY; strovl ovl(buf, new_len); ovl.copy(string_value.get_strref()); free(string_value.charstr()); string_value.set_overlay(buf, new_len); } string_const.clear(); string_value.append(append); return STATUS_OK; } StatusCode Asm::ParseStringOp(StringSymbol *pStr, strref line) { line.skip_whitespace(); if (line[0] == '+') ++line; for (;;) { line.skip_whitespace(); if (line[0] == '"') { strref substr = line.between('"', '"'); line += substr.get_len() + 2; pStr->Append(substr); } else { strref label = line.split_range(Merlin() ? label_end_char_range_merlin : label_end_char_range); if (StringSymbol *pStr2 = GetString(label)) pStr->Append(pStr2->get()); else if (Label *pLabel = GetLabel(label)) { if (!pLabel->evaluated) return ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY; strown<32> lblstr; lblstr.sprintf("$%x", pLabel->value); pStr->Append(lblstr.get_strref()); } else break; } line.skip_whitespace(); if (!line || line[0] != '+') break; ++line; line.skip_whitespace(); } return STATUS_OK; } StatusCode Asm::StringAction(StringSymbol *pStr, strref line) { line.skip_whitespace(); if (line[0] == '+' && line[1] == '=') { // append strings line += 2; line.skip_whitespace(); return ParseStringOp(pStr, line); } else if (line[0] == '=') { ++line; line.skip_whitespace(); pStr->clear(); return ParseStringOp(pStr, line); } strref str = pStr->string_value.valid() ? pStr->string_value.get_strref() : pStr->string_const; if (!str) { return STATUS_OK; } char *macro = (char*)malloc(str.get_len()); strovl mac(macro, str.get_len()); mac.copy(str); mac.replace("\\n", "\n"); loadedData.push_back(macro); PushContext(contextStack.curr().source_name, mac.get_strref(), mac.get_strref()); return STATUS_OK; } // // // CONDITIONAL ASSEMBLY // // // Encountered #if or #ifdef, return true if assembly is enabled bool Asm::NewConditional() { if (conditional_nesting[conditional_depth]) { conditional_nesting[conditional_depth]++; return false; } return true; } // Encountered #endif, close out the current conditional void Asm::CloseConditional() { if (conditional_depth>contextStack.curr().conditional_ctx) conditional_depth--; else conditional_consumed[conditional_depth] = false; } // Check if this conditional will nest the assembly (a conditional is already consumed) void Asm::CheckConditionalDepth() { if (conditional_consumed[conditional_depth]) { conditional_depth++; conditional_source[conditional_depth] = contextStack.curr().read_source.get_line(); conditional_consumed[conditional_depth] = false; conditional_nesting[conditional_depth] = 0; } } // This conditional block is going to be assembled, mark it as consumed void Asm::ConsumeConditional() { conditional_source[conditional_depth] = contextStack.curr().read_source.get_line(); conditional_consumed[conditional_depth] = true; } // This conditional block is not going to be assembled so mark that it is nesting void Asm::SetConditional() { conditional_source[conditional_depth] = contextStack.curr().read_source.get_line(); conditional_nesting[conditional_depth] = 1; } // Returns true if assembly is currently enabled bool Asm::ConditionalAsm() { return conditional_nesting[conditional_depth]==0; } // Returns true if this conditional has a block that has already been assembled bool Asm::ConditionalConsumed() { return conditional_consumed[conditional_depth]; } // Returns true if this conditional can be consumed bool Asm::ConditionalAvail() { return conditional_nesting[conditional_depth]==1 && !conditional_consumed[conditional_depth]; } // This conditional block is enabled and the prior wasn't void Asm::EnableConditional(bool enable) { if (enable) { conditional_nesting[conditional_depth] = 0; conditional_consumed[conditional_depth] = true; } } // Conditional else that does not enable block void Asm::ConditionalElse() { if (conditional_consumed[conditional_depth]) conditional_nesting[conditional_depth]++; } // Conditional statement evaluation (true/false) StatusCode Asm::EvalStatement(strref line, bool &result) { struct EvalContext etx; SetEvalCtxDefaults(etx); bool invert = line.get_first()=='!'; if (invert) ++line; int value; if (STATUS_OK != EvalExpression(line, etx, value)) return ERROR_CONDITION_COULD_NOT_BE_RESOLVED; result = (value!=0 && !invert) || (value==0 && invert); return STATUS_OK; } // Add a folder for including files void Asm::AddIncludeFolder(strref path) { if (!path) return; for (std::vector<strref>::const_iterator i = includePaths.begin(); i!=includePaths.end(); ++i) { if (path.same_str(*i)) return; } if (includePaths.size()==includePaths.capacity()) includePaths.reserve(includePaths.size() + 16); includePaths.push_back(path); } // unique key binary search int LookupOpCodeIndex(uint32_t hash, OPLookup *lookup, int count) { int first = 0; while (count!=first) { int index = (first+count)/2; uint32_t read = lookup[index].op_hash; if (hash==read) { return index; } else if (hash>read) first = index+1; else count = index; } return -1; // index not found } // Encountered a REPT or LUP StatusCode Asm::Directive_Rept(strref line) { SourceContext &ctx = contextStack.curr(); strref read_source = ctx.read_source; if (read_source.is_substr(line.get())) { read_source.skip(strl_t(line.get() - read_source.get())); strref expression; if (Merlin() || end_macro_directive) { expression = line; // Merlin repeat body begins next line read_source.line(); } else { int block = read_source.find('{'); if (block<0) return ERROR_REPT_MISSING_SCOPE; expression = read_source.get_substr(0, block); read_source += block; read_source.skip_whitespace(); } expression.trim_whitespace(); int count; struct EvalContext etx; SetEvalCtxDefaults(etx); if (STATUS_OK != EvalExpression(expression, etx, count)) return ERROR_REPT_COUNT_EXPRESSION; strref recur; if (Merlin() || end_macro_directive) { recur = read_source; // Merlin repeat body ends at "--^" while (strref next_line = read_source.line()) { next_line = next_line.before_or_full(';'); next_line = next_line.before_or_full(c_comment); int term = next_line.find(end_macro_directive ? "endr" : "--^"); if (term >= 0) { recur = recur.get_substr(0, strl_t(next_line.get() + term - recur.get())); break; } } } else recur = read_source.scoped_block_skip(); ctx.next_source = read_source; PushContext(ctx.source_name, ctx.source_file, recur, count); } return STATUS_OK; } // macro: create an assembler macro StatusCode Asm::Directive_Macro(strref line) { strref read_source = contextStack.curr().read_source.get_skip_ws(); if (!Merlin() && read_source.is_substr(line.get())) read_source.skip(strl_t(line.get()-read_source.get())); if (read_source) { StatusCode error = AddMacro(read_source, contextStack.curr().source_name, contextStack.curr().source_file, read_source); contextStack.curr().next_source = read_source; return error; } return STATUS_OK; } // string: create a symbolic string StatusCode Asm::Directive_String(strref line) { line.skip_whitespace(); strref string_name = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0); if (line[0]=='=' || keyword_equ.is_prefix_word(line)) { line.next_word_ws(); strref substr = line; if (line[0] == '"') { substr = line.between('"', '"'); line += substr.get_len() + 2; StringSymbol *pStr = AddString(string_name, substr); if (pStr == nullptr) return ERROR_OUT_OF_MEMORY; line.skip_whitespace(); if (line[0] == '+') return ParseStringOp(pStr, line); } else { StringSymbol *pStr = AddString(string_name, strref()); return ParseStringOp(pStr, line); } } else { if (!AddString(string_name, strref())) return ERROR_OUT_OF_MEMORY; } return STATUS_OK; } StatusCode Asm::Directive_Function(strref line) { line.skip_whitespace(); strref function_name = line.split_label(), params; line.skip_whitespace(); if (line.get_first() == '(') { params = line.scoped_block_comment_skip(); params.trim_whitespace(); } line.skip_whitespace(); userFunctions.Add(function_name, params, line); return STATUS_OK; } StatusCode Asm::Directive_Undef(strref line) { strref name = line.split_range_trim(Merlin() ? label_end_char_range_merlin : label_end_char_range); uint32_t name_hash = name.fnv1a(); uint32_t index = FindLabelIndex(name_hash, labels.getKeys(), labels.count()); while (index < labels.count() && name_hash == labels.getKey(index)) { if (name.same_str(labels.getValue(index).label_name)) { labels.remove(index); return STATUS_OK; } index++; } index = FindLabelIndex(name_hash, strings.getKeys(), strings.count()); while (index < strings.count() && name_hash == strings.getKey(index)) { if (name.same_str(strings.getValue(index).string_name)) { StringSymbol str = strings.getValue(index); if (str.string_value.cap()) { free(str.string_value.charstr()); str.string_value.invalidate(); } strings.remove(index); return STATUS_OK; } index++; } return STATUS_OK; } // include: read in a source file and assemble at this point StatusCode Asm::Directive_Include(strref line) { strref file = line.between('"', '"'); if (!file) // MERLIN: No quotes around PUT filenames file = line.split_range(filename_end_char_range); size_t size = 0; char *buffer = LoadText(file, size); if (buffer) { loadedData.push_back(buffer); strref src(buffer, strl_t(size)); PushContext(file, src, src); } else if (Merlin()) { // MERLIN include file name rules if (file[0] >= '!' && file[0] <= '&') buffer = LoadText(file + 1, size); if (buffer) { loadedData.push_back(buffer); // MERLIN: prepend with !-& to not auto-prepend with T. strref src(buffer, strl_t(size)); PushContext(file+1, src, src); } else { strown<512> fileadd(file[0]>='!' && file[0]<='&' ? (file+1) : file); fileadd.append(".s"); buffer = LoadText(fileadd.get_strref(), size); if (buffer) { loadedData.push_back(buffer); // MERLIN: !+filename appends .S to filenames strref src(buffer, strl_t(size)); PushContext(file, src, src); } else { fileadd.copy("T."); // MERLIN: just filename prepends T. to filenames fileadd.append(file[0]>='!' && file[0]<='&' ? (file+1) : file); buffer = LoadText(fileadd.get_strref(), size); if (buffer) { loadedData.push_back(buffer); strref src(buffer, strl_t(size)); PushContext(file, src, src); } } } } if (!size) return ERROR_COULD_NOT_INCLUDE_FILE; return STATUS_OK; } // incbin: import binary data in place StatusCode Asm::Directive_Incbin(strref line, int skip, int len) { line = line.between('"', '"'); strown<512> filename(line); size_t size = 0; if (char *buffer = LoadBinary(line, size)) { int bin_size = (int)size - skip; if (len && bin_size>len) bin_size = len; if (bin_size>0) AddBin((const uint8_t*)buffer+skip, bin_size); free(buffer); return STATUS_OK; } return ERROR_COULD_NOT_INCLUDE_FILE; } // import is a catch-all file reference StatusCode Asm::Directive_Import(strref line) { line.skip_whitespace(); int skip = 0; // binary import skip this amount int len = 0; // binary import load up to this amount strref param; // read out skip & max len parameters int q = line.find('"'); if (q>=0) { param = line + q; param.split_lang(); param.trim_whitespace(); if (param[0]==',') { ++param; param.skip_whitespace(); if (param) { struct EvalContext etx; SetEvalCtxDefaults(etx); EvalExpression(param.split_token_trim_track_parens(','), etx, skip); if (param) { EvalExpression(param, etx, len); } } } } if (line[0]=='"') return Directive_Incbin(line, skip, len); else if (import_source.is_prefix_word(line)) { line += import_source.get_len(); line.skip_whitespace(); return Directive_Include(line); } else if (import_binary.is_prefix_word(line)) { line += import_binary.get_len(); line.skip_whitespace(); return Directive_Incbin(line, skip, len); } else if (import_c64.is_prefix_word(line)) { line += import_c64.get_len(); line.skip_whitespace(); return Directive_Incbin(line, 2+skip, len); // 2 = load address skip size } else if (import_text.is_prefix_word(line)) { line += import_text.get_len(); line.skip_whitespace(); strref text_type = "petscii"; while (line[0]!='"') { strref word = line.get_word_ws(); if (word.same_str("petscii") || word.same_str("petscii_shifted")) { text_type = line.get_word_ws(); line += text_type.get_len(); line.skip_whitespace(); } else if (StringSymbol *pStr = GetString(line.get_word_ws())) { line = pStr->get(); break; } } CurrSection().AddText(line, text_type); return STATUS_OK; } else if (import_object.is_prefix_word(line)) { line += import_object.get_len(); line.trim_whitespace(); return ReadObjectFile(line[0]=='"' ? line.between('"', '"') : line); } else if (import_symbols.is_prefix_word(line)) { line += import_symbols.get_len(); line.skip_whitespace(); return IncludeSymbols(line); } return STATUS_OK; } // org / pc: current address of code StatusCode Asm::Directive_ORG(strref line) { int addr; if (line[0]=='=') ++line; else if (keyword_equ.is_prefix_word(line)) // optional '=' or equ line.next_word_ws(); line.skip_whitespace(); struct EvalContext etx; SetEvalCtxDefaults(etx); StatusCode error = EvalExpression(line, etx, addr); if (error != STATUS_OK) return (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) ? ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY : error; // Section immediately followed by ORG reassigns that section to be fixed Section &currSection = CurrSection(); if (currSection.size()==0 && !currSection.IsDummySection() && !currSection.address_assigned) { if (currSection.type == ST_ZEROPAGE && addr >= 0x100) return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE; AssignAddressToSection(SectionId(), addr); } else SetSection(strref(), addr); return STATUS_OK; } // load: address for target to load code at StatusCode Asm::Directive_LOAD(strref line) { int addr; if (line[0]=='=' || keyword_equ.is_prefix_word(line)) line.next_word_ws(); struct EvalContext etx; SetEvalCtxDefaults(etx); StatusCode error = EvalExpression(line, etx, addr); if (error != STATUS_OK) return (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) ? ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY : error; CurrSection().SetLoadAddress(addr); return STATUS_OK; } StatusCode Asm::Directive_MERGE(strref line) { int first_section = -1; strref section_name = line.split_label(); // get the first section that matches the first name and has an assigned address for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) { const Section §ion = allSections[section_id]; if (!section.IsMergedSection()&§ion.name.same_str(section_name)) { if (first_section<0||!allSections[first_section].IsRelativeSection()) { first_section = (int)section_id; } } } if (first_section<0) { return ERROR_NOT_A_SECTION; } // merge all sections as defined by the line while (section_name) { for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) { const Section §ion = allSections[section_id]; if (section_id!=first_section&&!section.IsMergedSection()&§ion.IsRelativeSection()) { if (section.name.same_str(section_name)) { StatusCode result = MergeSections(first_section, (int)section_id); if (result!=STATUS_OK) { return result; } } } } if (line[0]==',') { ++line; } section_name = line.split_label(); } return STATUS_OK; } // MERLIN version of AD_LINK, which is more like AD_INCOBJ + link to current section StatusCode Asm::Directive_LNK(strref line) { strref file = line.between('"', '"'); if (!file) // MERLIN: No quotes around include filenames file = line.split_range(filename_end_char_range); int section_id = SectionId(); StatusCode error = ReadObjectFile(file, SectionId()); // restore current section current_section = &allSections[section_id]; return error; } // this stores a string that when matched with a label will make that label external StatusCode Asm::Directive_XDEF(strref line) { line.trim_whitespace(); if (strref xdef = line.split_range(Merlin() ? label_end_char_range_merlin : label_end_char_range)) { char f = xdef.get_first(); char e = xdef.get_last(); if (f != '.' && f != '!' && f != '@' && e != '$') { uint32_t hash = xdef.fnv1a(); uint32_t pos = FindLabelIndex(hash, xdefs.getKeys(), xdefs.count()); while (pos < xdefs.count() && xdefs.getKey(pos) == hash) { if (xdefs.getValue(pos).same_str_case(xdef)) return STATUS_OK; ++pos; } xdefs.insert(pos, hash); xdefs.getValues()[pos] = xdef; } } return STATUS_OK; } StatusCode Asm::Directive_XREF(strref label) { // XREF already defined label => no action if (!GetLabel(label)) { Label *pLabelXREF = AddLabel(label.fnv1a()); pLabelXREF->label_name = label; pLabelXREF->pool_name.clear(); pLabelXREF->section = -1; // address labels are based on section pLabelXREF->value = 0; pLabelXREF->evaluated = true; pLabelXREF->pc_relative = true; pLabelXREF->external = true; pLabelXREF->constant = false; pLabelXREF->reference = true; pLabelXREF->referenced = false; // referenced is only within the current object file } return STATUS_OK; } // dc.b, dc.w, dc.t, dc.l, ADR, ADRL, bytes, words, long StatusCode Asm::Directive_DC(strref line, int width, strref source_file) { struct EvalContext etx; SetEvalCtxDefaults(etx); line.trim_whitespace(); while (strref exp_dc = line.split_token_trim_track_parens(',')) { int value = 0; if (!CurrSection().IsDummySection()) { if (Merlin() && exp_dc.get_first() == '#') // MERLIN allows for an immediate declaration on data ++exp_dc; StatusCode error = EvalExpression(exp_dc, etx, value); if (error > STATUS_XREF_DEPENDENT) break; else if (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], exp_dc, source_file, width == 1 ? LateEval::LET_BYTE : (width == 2 ? LateEval::LET_ABS_REF : (width == 3 ? LateEval::LET_ABS_L_REF : LateEval::LET_ABS_4_REF))); else if (error == STATUS_RELATIVE_SECTION) { value = 0; CurrSection().AddReloc(lastEvalValue, CurrSection().DataOffset(), lastEvalSection, (int8_t)width, (int8_t)lastEvalShift); } } uint8_t bytes[4] = { (uint8_t)value, (uint8_t)(value >> 8), (uint8_t)(value >> 16), (uint8_t)(value >> 24) }; AddBin(bytes, width); } return STATUS_OK; } // ds/ds.b/ds.w/ds.t/ds.l StatusCode Asm::Directive_DS(strref line) { int width = 1; int value; if (line.get_first() == '.' && strref::is_alphabetic(line[1])) { switch (strref::tolower(line[1])) { case 'b': break; case 'w': width = 2; break; case 't': width = 3; break; case 'l': width = 4; break; } line += 2; line.skip_whitespace(); } struct EvalContext etx; SetEvalCtxDefaults(etx); strref size = line.split_token_trim_track_parens(','); if (STATUS_OK != EvalExpression(size, etx, value)) return ERROR_DS_MUST_EVALUATE_IMMEDIATELY; int fill = 0; if (line && STATUS_OK != EvalExpression(line, etx, fill)) return ERROR_DS_MUST_EVALUATE_IMMEDIATELY; value *= width; if (value > 0) { for (int n = 0; n < value; n++) AddByte(fill); } else if (value) { CurrSection().AddAddress(value); if (CurrSection().type == ST_ZEROPAGE && CurrSection().address > 0x100) return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE; } return STATUS_OK; } StatusCode Asm::Directive_ALIGN(strref line) { if (line) { if (line[0] == '=' || keyword_equ.is_prefix_word(line)) line.next_word_ws(); struct EvalContext etx; SetEvalCtxDefaults(etx); int value; int status = EvalExpression(line, etx, value); if (status == STATUS_NOT_READY || status == STATUS_XREF_DEPENDENT) return ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY; if (status == STATUS_OK && value>0) { if (CurrSection().address_assigned) { int add = (CurrSection().GetPC() + value - 1) % value; for (int a = 0; a < add; a++) AddByte(0); } else CurrSection().align_address = value; } } return STATUS_OK; } StatusCode Asm::Directive_EVAL(strref line) { int value = 0; strref description = line.find(':') >= 0 ? line.split_token_trim(':') : strref(); line.trim_whitespace(); struct EvalContext etx; SetEvalCtxDefaults(etx); strref lab1 = line; lab1 = lab1.split_token_any_trim(Merlin() ? label_end_char_range_merlin : label_end_char_range); StringSymbol *pStr = line.same_str_case(lab1) ? GetString(lab1) : nullptr; if (line && EvalExpression(line, etx, value) == STATUS_OK) { if (description) { if (pStr != nullptr) { printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = \"" STRREF_FMT "\" = $%x\n", contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line), STRREF_ARG(pStr->get()), value); } else { printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = $%x\n", contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line), value); } } else { if (pStr != nullptr) { printf("EVAL(%d): \"" STRREF_FMT "\" = \"" STRREF_FMT "\" = $%x\n", contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line), STRREF_ARG(pStr->get()), value); } else { printf("EVAL(%d): \"" STRREF_FMT "\" = $%x\n", contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line), value); } } } else if (description) { if (pStr != nullptr) { printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = \"" STRREF_FMT "\"\n", contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line), STRREF_ARG(pStr->get())); } else { printf("EVAL(%d): \"" STRREF_FMT ": " STRREF_FMT"\"\n", contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line)); } } else { if (pStr != nullptr) { printf("EVAL(%d): \"" STRREF_FMT "\" = \"" STRREF_FMT "\"\n", contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line), STRREF_ARG(pStr->get())); } else { printf("EVAL(%d): \"" STRREF_FMT "\"\n", contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line)); } } return STATUS_OK; } StatusCode Asm::Directive_HEX(strref line) { uint8_t b = 0, v = 0; while (line) { // indeterminable length, can't read hex to int char c = *line.get(); ++line; if (c == ',') { if (b) // probably an error but seems safe AddByte(v); b = 0; line.skip_whitespace(); } else { if (c >= '0' && c <= '9') v = (v << 4) + (c - '0'); else if (c >= 'A' && c <= 'Z') v = (v << 4) + (c - 'A' + 10); else if (c >= 'a' && c <= 'z') v = (v << 4) + (c - 'a' + 10); else break; b ^= 1; if (!b) AddByte(v); } } if (b) return ERROR_HEX_WITH_ODD_NIBBLE_COUNT; return STATUS_OK; } StatusCode Asm::Directive_ENUM_STRUCT(strref line, AssemblerDirective dir) { strref read_source = contextStack.curr().read_source; if (read_source.is_substr(line.get())) { strref struct_name = line.get_word(); line.skip(struct_name.get_len()); line.skip_whitespace(); read_source.skip(strl_t(line.get() - read_source.get())); if (read_source[0] == '{') { if (dir == AD_STRUCT) BuildStruct(struct_name, read_source.scoped_block_skip()); else BuildEnum(struct_name, read_source.scoped_block_skip()); } else return dir == AD_STRUCT ? ERROR_STRUCT_CANT_BE_ASSEMBLED : ERROR_ENUM_CANT_BE_ASSEMBLED; contextStack.curr().next_source = read_source; } else return ERROR_STRUCT_CANT_BE_ASSEMBLED; return STATUS_OK; } // Action based on assembler directive StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref source_file) { StatusCode error = STATUS_OK; if (!ConditionalAsm()) { // If conditionally blocked from assembling only check conditional directives if (dir!=AD_IF && dir!=AD_IFDEF && dir!=AD_ELSE && dir!=AD_ELIF && dir!=AD_ELSE && dir!=AD_ENDIF) return STATUS_OK; } struct EvalContext etx; SetEvalCtxDefaults(etx); switch (dir) { case AD_CPU: for (int c = 0; c < nCPUs; c++) { if (line.same_str(aCPUs[c].name)) { if (c != cpu) SetCPU((CPUIndex)c); return STATUS_OK; } } return ERROR_CPU_NOT_SUPPORTED; case AD_EXPORT: if (import_means_xref) { return Directive_XDEF(line); } line.trim_whitespace(); CurrSection().export_append = line.split_label(); break; case AD_ORG: return Directive_ORG(line); case AD_LOAD: return Directive_LOAD(line); case AD_SECTION: SetSection(line); break; case AD_MERGE: return Directive_MERGE(line); case AD_LINK: return LinkSections(line.get_trimmed_ws()); case AD_LNK: return Directive_LNK(line); case AD_INCOBJ: { strref file = line.between('"', '"'); if (!file) // MERLIN: No quotes around include filenames file = line.split_range(filename_end_char_range); error = ReadObjectFile(file); break; } case AD_XDEF: return Directive_XDEF(line.get_trimmed_ws()); case AD_XREF: Directive_XREF(line.split_range_trim( Merlin() ? label_end_char_range_merlin : label_end_char_range)); break; case AD_ENT: // MERLIN version of xdef, makes most recently defined label external if (Label *pLastLabel = GetLabel(last_label)) pLastLabel->external = true; break; case AD_EXT: Directive_XREF(last_label); break; case AD_ALIGN: // align: align address to multiple of value, fill space with 0 return Directive_ALIGN(line); case AD_EVAL: // eval: display the result of an expression in stdout return Directive_EVAL(line); case AD_BYTES: // bytes: add bytes by comma separated values/expressions return Directive_DC(line, 1, source_file); case AD_WORDS: // words: add words (16 bit values) by comma separated values return Directive_DC(line, 2, source_file); case AD_ADR: // ADR: MERLIN store 3 byte word return Directive_DC(line, 3, source_file); case AD_ADRL: // ADRL: MERLIN store 4 byte word return Directive_DC(line, 4, source_file); case AD_DC: { int width = 1; if (line[0]=='.') { ++line; switch (strref::tolower(line.get_first())) { case 'b': width = 1; break; case 'w': width = 2; break; case 't': width = 3; break; case 'l': width = 4; break; default: return ERROR_BAD_TYPE_FOR_DECLARE_CONSTANT; } ++line; } return Directive_DC(line, width, source_file); } case AD_HEX: return Directive_HEX(line); case AD_EJECT: line.clear(); break; case AD_USR: line.clear(); break; case AD_CYC: list_flags |= cycle_counter_level ? ListLine::CYCLES_STOP : ListLine::CYCLES_START; cycle_counter_level = !!cycle_counter_level; break; case AD_SAV: line.trim_whitespace(); if (line.has_prefix(export_base_name)) line.skip(export_base_name.get_len()); if (line) CurrSection().export_append = line.split_label(); AssignAddressToGroup(); break; case AD_XC: // XC: MERLIN version of setting CPU if (strref("off").is_prefix_word(line)) SetCPU(CPU_6502); else if (strref("xc").is_prefix_word(line)) SetCPU(CPU_65816); else if (cpu==CPU_65C02) SetCPU(CPU_65816); else SetCPU(CPU_65C02); break; case AD_TEXT: { // text: add text within quotes strref text_prefix; if (line[0]=='[') { strref str = line.scoped_block_skip().get_trimmed_ws(); if (StringSymbol *StringSym = GetString(str)) { line.skip_whitespace(); if (line[0] == '"') line = line.between('"', '"'); CurrSection().AddIndexText(StringSym, line); break; } } while (line[0] != '"') { strref word = line.get_word_ws(); if (word.same_str("petscii") || word.same_str("petscii_shifted")) { text_prefix = line.get_word_ws(); line += text_prefix.get_len(); line.skip_whitespace(); } else if (StringSymbol *pStr = GetString(line.get_word_ws())) { line = pStr->get(); break; } } if (line[0] == '"') line = line.between('"', '"'); CurrSection().AddText(line, text_prefix); break; } case AD_MACRO: error = Directive_Macro(line); break; case AD_INCLUDE: // assemble another file in place return Directive_Include(line); case AD_INCBIN: return Directive_Incbin(line); case AD_IMPORT: if (import_means_xref) { return Directive_XREF(line); } return Directive_Import(line); case AD_LABEL: case AD_CONST: { line.trim_whitespace(); strref label = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0); if (line[0]=='=' || keyword_equ.is_prefix_word(line)) { line.next_word_ws(); AssignLabel(label, line, dir==AD_CONST); } else error = ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT; break; } case AD_STRING: return Directive_String(line); case AD_FUNCTION: return Directive_Function(line); case AD_UNDEF: return Directive_Undef(line); case AD_INCSYM: return IncludeSymbols(line); case AD_LABPOOL: { strref name = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0); AddLabelPool(name, line); break; } case AD_IF: if (NewConditional()) { // Start new conditional block CheckConditionalDepth(); // Check if nesting bool conditional_result; error = EvalStatement(line, conditional_result); if (conditional_result) ConsumeConditional(); else SetConditional(); } break; case AD_IFDEF: if (NewConditional()) { // Start new conditional block CheckConditionalDepth(); // Check if nesting // ifdef doesn't need to evaluate the value, just determine if it exists or not strref label = line.split_range_trim(label_end_char_range); if( GetLabel(label, etx.file_ref) ) ConsumeConditional(); else SetConditional(); } break; case AD_IFNDEF: if (NewConditional()) { // Start new conditional block CheckConditionalDepth(); // Check if nesting // ifdef doesn't need to evaluate the value, just determine if it exists or not strref label = line.split_range_trim(label_end_char_range); if (!GetLabel(label, etx.file_ref)) ConsumeConditional(); else SetConditional(); } break; case AD_IFCONST: if (NewConditional()) { // Start new conditional block CheckConditionalDepth(); // Check if nesting // ifdef doesn't need to evaluate the value, just determine if it exists or not strref label_name = line.split_range_trim(label_end_char_range); if (Label* label = GetLabel(label_name, etx.file_ref)) { if (label->constant) { ConsumeConditional(); } else { SetConditional(); } } else { SetConditional(); } } break; case AD_IFBLANK: if (NewConditional()) { // Start new conditional block CheckConditionalDepth(); // Check if nesting line.trim_whitespace(); if (line.is_empty()) ConsumeConditional(); else SetConditional(); } break; case AD_IFNBLANK: if (NewConditional()) { // Start new conditional block CheckConditionalDepth(); // Check if nesting line.trim_whitespace(); if (!line.is_empty()) ConsumeConditional(); else SetConditional(); } break; case AD_ELSE: if (ConditionalAsm()) { if (ConditionalConsumed()) ConditionalElse(); else error = ERROR_ELSE_WITHOUT_IF; } else if (ConditionalAvail()) EnableConditional(true); break; case AD_ELIF: if (ConditionalAsm()) { if (ConditionalConsumed()) ConditionalElse(); else error = ERROR_ELSE_WITHOUT_IF; } else if (ConditionalAvail()) { bool conditional_result; error = EvalStatement(line, conditional_result); EnableConditional(conditional_result); } break; case AD_ENDIF: if (ConditionalAsm()) { if (ConditionalConsumed()) CloseConditional(); else error = ERROR_ENDIF_WITHOUT_CONDITION; } else { conditional_nesting[conditional_depth]--; if (ConditionalAsm()) CloseConditional(); } break; case AD_ENUM: case AD_STRUCT: return Directive_ENUM_STRUCT(line, dir); case AD_REPT: return Directive_Rept(line); case AD_INCDIR: AddIncludeFolder(line.between('"', '"')); break; case AD_A16: // A16: Set 16 bit accumulator mode accumulator_16bit = true; break; case AD_A8: // A8: Set 8 bit accumulator mode accumulator_16bit = false; break; case AD_XY16: // A16: Set 16 bit accumulator mode index_reg_16bit = true; break; case AD_XY8: // A8: Set 8 bit accumulator mode index_reg_16bit = false; break; case AD_MX: if (line) { line.trim_whitespace(); int value = 0; error = EvalExpression(line, etx, value); index_reg_16bit = !(value&1); accumulator_16bit = !(value&2); } break; case AD_ABORT: line.trim_whitespace(); if (line) printf("Assembler aborted: " STRREF_FMT "\n", STRREF_ARG(line)); return ERROR_ABORTED; case AD_LST: line.clear(); break; case AD_DUMMY: line.trim_whitespace(); if (line) { int reorg; if (STATUS_OK == EvalExpression(line, etx, reorg)) { DummySection(reorg); break; } } DummySection(); break; case AD_DUMMY_END: while (CurrSection().IsDummySection()) { EndSection(); if (SectionId()==0) break; } break; case AD_DS: return Directive_DS(line); case AD_SCOPE: directive_scope_depth++; return EnterScope(); case AD_ENDSCOPE: directive_scope_depth--; return ExitScope(); case AD_PUSH: line.trim_whitespace(); if (Label *label = GetLabel(line)) { symbolStacks.PushSymbol(label); return STATUS_OK; } else if( StringSymbol* string = GetString(line)) { symbolStacks.PushSymbol(string); return STATUS_OK; } return ERROR_UNABLE_TO_PROCESS; case AD_PULL: line.trim_whitespace(); if (Label *label = GetLabel(line)) { return symbolStacks.PullSymbol(label); } else if (StringSymbol* string = GetString(line)) { return symbolStacks.PullSymbol(string); } return ERROR_UNABLE_TO_PROCESS; } return error; } // Make an educated guess at the intended address mode from an opcode argument StatusCode Asm::GetAddressMode(strref line, bool flipXY, uint32_t &validModes, AddrMode &addrMode, int &len, strref &expression) { bool force_zp = false; bool force_24 = false; bool force_abs = false; bool need_more = true; bool first = true; strref arg, deco; len = 0; while (need_more) { need_more = false; line.skip_whitespace(); uint8_t c = line.get_first(); if (!c) { addrMode = AMB_NON; } if( c == '[' || c == '(' ) { strref block_suffix( line.get(), line.scoped_block_comment_len() ); strref suffix = line.get_skipped( block_suffix.get_len() ); suffix.trim_whitespace(); if( suffix.get_first() == ',' ) { ++suffix; suffix.skip_whitespace(); } else { suffix.clear(); } ++block_suffix; block_suffix.clip(1); block_suffix.trim_whitespace(); ++line; line.skip_whitespace(); strref block = block_suffix.split_token_trim_track_parens( ',' ); validModes &= AMM_ZP_REL_X | AMM_ZP_Y_REL | AMM_REL | AMM_ZP_REL | AMM_REL_X | AMM_ZP_REL_L | AMM_ZP_REL_Y_L | AMM_STK_REL_Y | AMM_REL_L; if( line.get_first() == '>' ) { // [>$aaaa] if( c == '[' ) { addrMode = AMB_REL_L; validModes &= AMM_REL_L; expression = block+1; } } else if( line.get_first() == '|' || line.get_first() == '!' && c == '(' ) { // (|$aaaa) or (|$aaaa,x) strref arg = block.after( ',' ); arg.skip_whitespace(); if( arg && ( arg.get_first() == 'x' || arg.get_first() == 'X' ) ) { addrMode = AMB_REL_X; validModes &= AMM_REL_X; expression = block.before( ',' ); } else { addrMode = AMB_REL; validModes &= AMM_REL; expression = block; } } else if( line.get_first() == '<' ) { // (<$aa) (<$aa),y (<$aa,x) (<$aa,s),y [<$aa] [<$aa],y if( suffix ) { if( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) { if( c == '(' ) { // (<$aa),y or (<$aa,s),y if( block_suffix && ( block_suffix.get_first() == 's' || block_suffix.get_first() == 'S' ) ) { expression = block+1; addrMode = AMB_STK_REL_Y; validModes &= AMM_STK_REL_Y; } else { expression = block+1; addrMode = AMB_ZP_Y_REL; validModes &= AMM_ZP_Y_REL; } } else { // [<$aa],y expression = block+1; addrMode = AMB_ZP_REL_Y_L; validModes &= AMM_ZP_REL_Y_L; } } else { return ERROR_BAD_ADDRESSING_MODE; } } else { // (<$aa) (<$aa,x) [<$aa] if( c == '[' ) { if( block.find( ',' ) >= 0 || suffix.get_first() == ',' ) { return ERROR_BAD_ADDRESSING_MODE; } expression = block+1; addrMode = AMB_ZP_REL_L; validModes &= AMM_ZP_REL_L; } else { if( block_suffix ) { if( block_suffix.get_first() != 'x' && block_suffix.get_first() != 'X' ) { return ERROR_BAD_ADDRESSING_MODE; } expression = block+1; addrMode = AMB_ZP_REL_X; validModes &= AMM_ZP_REL_X; } else { expression = block+1; addrMode = AMB_ZP_REL; validModes &= AMM_ZP_REL; } } } } else { // no <, |, ! or > decorator inside (...) or [...] if( c == '[' && ( block_suffix.get_first() == 's' || block_suffix.get_first() == 'S' ) ) { if( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) { expression = block; addrMode = AMB_STK_REL_Y; validModes &= AMM_STK_REL_Y; } else { return ERROR_BAD_ADDRESSING_MODE; } } else if( block_suffix.get_first() == 'x' || block_suffix.get_first() == 'X' ) { // ($aa,x) ($aaaa,x) if( c == '[' ) { return ERROR_BAD_ADDRESSING_MODE; } expression = block; switch( validModes & ( AMM_ZP_REL_X | AMM_REL_X ) ) { case AMM_ZP_REL_X: addrMode = AMB_ZP_REL_X; validModes = AMM_ZP_REL_X; break; case AMM_REL_X: addrMode = AMB_REL_X; validModes = AMM_REL_X; break; default: addrMode = force_zp ? AMB_ZP_REL_X : AMB_REL_X; validModes &= force_zp ? AMM_ZP_REL_X : ( force_abs ? AMM_ZP_REL_X : ( AMM_ZP_REL_X | AMM_REL_X ) ); break; } } else if( suffix && ( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) ) { if( c == '[' ) { expression = block; addrMode = AMB_ZP_REL_Y_L; validModes &= AMM_ZP_REL_Y_L; } else { // ($aa),y expression = block; addrMode = AMB_ZP_Y_REL; validModes &= AMM_ZP_Y_REL; } } else { // ($aa), ($aaaa), [$aa], [$aaaa] if( c == '[' ) { // [$aa], [$aaaa] expression = block; addrMode = force_zp ? AMB_ZP_REL_L : AMB_REL_L; validModes &= force_zp ? AMM_ZP_REL_L : ( force_abs ? AMM_REL_L : ( AMM_ZP_REL_L | AMM_REL_L ) ); } else { // ($aa), ($aaaa) expression = block; addrMode = force_zp ? AMB_ZP_REL : AMB_REL; validModes &= force_zp ? AMM_ZP_REL : ( force_abs ? AMM_REL : ( AMM_ZP_REL | AMM_REL ) ); } } } expression.trim_whitespace(); } else if (c == '<' ) { // force zero page not indirect ++line; line.trim_whitespace(); strref suffix = line.after(','); suffix.skip_whitespace(); expression = line.before_or_full(','); expression.trim_whitespace(); if( suffix ) { if( suffix.get_first() == 's' || suffix.get_first() == 'S' ) { addrMode = AMB_STK; validModes &= AMM_STK; // not correct usage of < but I'll allow it. } else if( suffix.get_first() == 'x' || suffix.get_first() == 'X' ) { addrMode = AMB_ZP_X; validModes &= AMM_ZP_X; } else { return ERROR_BAD_ADDRESSING_MODE; } } else { addrMode = AMB_ZP; validModes &= AMM_ZP; } } else if (c == '<') { validModes &= AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ZP_Y_REL | AMM_ZP_REL | AMM_ZP_ABS | AMM_ZP_REL_L | AMM_ZP_REL_Y_L | AMM_FLIPXY; } else if( cpu == CPU_65816 && ( c == '|' /*|| c == '!'*/ ) ) { // disabling ! for now since it conflicts with scope start ++line; line.trim_whitespace(); strref suffix = line.after( ',' ); suffix.skip_whitespace(); expression = line.before_or_full( ',' ); expression.trim_whitespace(); addrMode = AMB_NON; if( suffix ) { if( suffix.get_first() == 'x' || suffix.get_first() == 'X' ) { addrMode = AMB_ABS_X; validModes &= AMM_ABS_X; } else if( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) { addrMode = AMB_ABS_Y; validModes &= AMM_ABS_Y; } } else { addrMode = AMB_ABS; validModes &= AMM_ABS; } } else if( cpu == CPU_65816 && c == '>' ) { ++line; line.trim_whitespace(); strref suffix = line.after( ',' ); suffix.skip_whitespace(); expression = line.before_or_full( ',' ); expression.trim_whitespace(); if( suffix ) { if( suffix.get_first() == 'x' || suffix.get_first() == 'X' ) { addrMode = AMB_ABS_L_X; validModes &= AMM_ABS_L_X; } else { return ERROR_BAD_ADDRESSING_MODE; } } else { addrMode = AMB_ABS_L; validModes &= AMM_ABS_L; } } else if (c == '#') { ++line; addrMode = AMB_IMM; validModes &= AMM_IMM | AMM_IMM_DBL_A | AMM_IMM_DBL_XY; expression = line; } else if (line) { if (line[0]=='.' && strref::is_ws(line[2])) { switch (strref::tolower(line[1])) { case 'z': force_zp = true; line += 3; need_more = true; len = 1; break; case 'b': line += 3; need_more = true; len = 1; validModes &= ~(AMM_IMM_DBL_A | AMM_IMM_DBL_XY); break; case 'w': line += 3; need_more = true; len = 2; break; case 'l': force_24 = true; line += 3; need_more = true; len = 3; break; case 'a': force_abs = true; line += 3; need_more = true; break; } } if (!need_more) { if( strref( "A" ).is_prefix_word( line ) ) { addrMode = AMB_ACC; } else { // absolute (zp, offs x, offs y) addrMode = force_24 ? AMB_ABS_L : (force_zp ? AMB_ZP : AMB_ABS); expression = line.split_token_trim_track_parens(','); if( force_abs ) { validModes &= AMM_ABS | AMM_ABS_X | AMM_ABS_Y | AMM_REL | AMM_REL_X; } if( force_zp ) { validModes &= AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ZP_Y_REL | AMM_ZP_REL | AMM_ZP_ABS | AMM_ZP_REL_L | AMM_ZP_REL_Y_L | AMM_FLIPXY; } if( line && (line[ 0 ] == 's' || line[ 0 ] == 'S') ) { addrMode = AMB_STK; } else { bool relX = line && (line[0]=='x' || line[0]=='X'); bool relY = line && (line[0]=='y' || line[0]=='Y'); if ((flipXY && relY) || (!flipXY && relX)) addrMode = force_24 ? AMB_ABS_L_X : (force_zp ? AMB_ZP_X : AMB_ABS_X); else if ((flipXY && relX) || (!flipXY && relY)) { if (force_zp) { return ERROR_INSTRUCTION_NOT_ZP; } addrMode = AMB_ABS_Y; } } } } } first = false; } return STATUS_OK; } // Push an opcode to the output buffer in the current section StatusCode Asm::AddOpcode(strref line, int index, strref source_file) { StatusCode error = STATUS_OK; strref expression; // allowed modes uint32_t validModes = opcode_table[index].modes; // instruction parameter length override int op_param = 0; // Get the addressing mode and the expression it refers to AddrMode addrMode; switch (validModes) { case AMC_BBR: addrMode = AMB_ZP_ABS; expression = line.split_token_trim_track_parens(','); if (!expression || !line) return ERROR_INVALID_ADDRESSING_MODE; break; case AMM_BRA: addrMode = AMB_ABS; expression = line; break; case AMM_ACC: case (AMM_ACC|AMM_NON): case AMM_NON: addrMode = AMB_NON; break; case AMM_BLK_MOV: addrMode = AMB_BLK_MOV; expression = line.before_or_full_track_parens(','); break; default: error = GetAddressMode(line, !!(validModes & AMM_FLIPXY), validModes, addrMode, op_param, expression); break; } int value = 0; int target_section = -1; int target_section_offs = -1; int8_t target_section_shift = 0; bool evalLater = false; if (expression) { struct EvalContext etx; SetEvalCtxDefaults(etx); if (validModes & (AMM_BRANCH | AMM_BRANCH_L)) etx.relative_section = SectionId(); error = EvalExpression(expression, etx, value); if (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) { evalLater = true; error = STATUS_OK; } else if (error == STATUS_RELATIVE_SECTION) { target_section = lastEvalSection; target_section_offs = lastEvalValue; target_section_shift = lastEvalShift; } else if (error != STATUS_OK) return error; } // check if address is in zero page range and should use a ZP mode instead of absolute if (!evalLater && value>=0 && value<0x100 && (error != STATUS_RELATIVE_SECTION || (target_section>=0 && allSections[target_section].type==ST_ZEROPAGE))) { switch (addrMode) { case AMB_ABS: if (validModes & AMM_ZP) addrMode = AMB_ZP; else if (validModes & AMM_ABS_L) addrMode = AMB_ABS_L; break; case AMB_ABS_X: if (validModes & AMM_ZP_X) addrMode = AMB_ZP_X; else if (validModes & AMM_ABS_L_X) addrMode = AMB_ABS_L_X; break; default: break; } } // Check if an explicit 24 bit address if (expression[0] == '$' && (expression + 1).len_hex()>4) { if (addrMode==AMB_ABS&&(validModes & AMM_ABS_L)) { addrMode = AMB_ABS_L; } else if (addrMode==AMB_ABS_X&&(validModes & AMM_ABS_L_X)) { addrMode = AMB_ABS_L_X; } } if (!(validModes & (1 << addrMode))) { if (addrMode==AMB_ZP_REL_X&&(validModes & AMM_REL_X)) { addrMode = AMB_REL_X; } else if (addrMode==AMB_REL&&(validModes & AMM_ZP_REL)) { addrMode = AMB_ZP_REL; } else if (addrMode==AMB_ABS&&(validModes & AMM_ABS_L)) { addrMode = AMB_ABS_L; } else if (addrMode==AMB_ABS_X&&(validModes & AMM_ABS_L_X)) { addrMode = AMB_ABS_L_X; } else if (addrMode==AMB_REL_L&&(validModes & AMM_ZP_REL_L)) { addrMode = AMB_ZP_REL_L; } else if (Merlin()&&addrMode==AMB_IMM && validModes==AMM_ABS) { addrMode = AMB_ABS; // Merlin seems to allow this } else if (Merlin()&&addrMode==AMB_ABS && validModes==AMM_ZP_REL) { addrMode = AMB_ZP_REL; // Merlin seems to allow this } else { return ERROR_INVALID_ADDRESSING_MODE; } } // Add the instruction and argument to the code if (error == STATUS_OK || error == STATUS_RELATIVE_SECTION) { uint8_t opcode = opcode_table[index].aCodes[addrMode]; StatusCode cap_status = CheckOutputCapacity(4); if (cap_status != STATUS_OK) return error; AddByte(opcode); CODE_ARG codeArg = CA_NONE; if (validModes & AMM_BRANCH_L) codeArg = CA_BRANCH_16; else if (validModes & AMM_BRANCH) codeArg = CA_BRANCH; else { switch (addrMode) { case AMB_ZP_REL_X: // 0 ($12:x) case AMB_ZP: // 1 $12 case AMB_ZP_Y_REL: // 4 ($12),y case AMB_ZP_X: // 5 $12,x case AMB_ZP_REL: // b ($12) case AMB_ZP_REL_L: // e [$02] case AMB_ZP_REL_Y_L: // f [$00],y case AMB_STK: // 12 $12,s case AMB_STK_REL_Y: // 13 ($12,s),y codeArg = CA_ONE_BYTE; break; case AMB_ABS_Y: // 6 $1234,y case AMB_ABS_X: // 7 $1234,x case AMB_ABS: // 3 $1234 case AMB_REL: // 8 ($1234) case AMB_REL_X: // c ($1234,x) case AMB_REL_L: // 14 [$1234] codeArg = CA_TWO_BYTES; break; case AMB_ABS_L: // 10 $e00001 case AMB_ABS_L_X: // 11 $123456,x codeArg = CA_THREE_BYTES; break; case AMB_ZP_ABS: // d $12, label codeArg = CA_BYTE_BRANCH; break; case AMB_BLK_MOV: // 15 $12,$34 codeArg = CA_TWO_ARG_BYTES; break; case AMB_IMM: // 2 #$12 codeArg = CA_ONE_BYTE; // check for double immediate if( validModes & ( AMM_IMM_DBL_A | AMM_IMM_DBL_XY ) ) { if( op_param ) { codeArg = op_param == 2 ? CA_TWO_BYTES : CA_ONE_BYTE; } else if( ( ( validModes&AMM_IMM_DBL_A ) && accumulator_16bit ) || ( ( validModes&AMM_IMM_DBL_XY ) && index_reg_16bit ) ) { codeArg = CA_TWO_BYTES; } else { codeArg = CA_ONE_BYTE; } } break; case AMB_ACC: // 9 A case AMB_NON: // a default: break; } } switch (codeArg) { case CA_ONE_BYTE: if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE); else if (error == STATUS_RELATIVE_SECTION) CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 1, target_section_shift); AddByte(value); break; case CA_TWO_BYTES: if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_ABS_REF); else if (error == STATUS_RELATIVE_SECTION) { CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 2, target_section_shift); value = 0; } AddWord(value); break; case CA_THREE_BYTES: if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_ABS_L_REF); else if (error == STATUS_RELATIVE_SECTION) { CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 3, target_section_shift); value = 0; } AddTriple(value); break; case CA_TWO_ARG_BYTES: { if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE); else if (error == STATUS_RELATIVE_SECTION) { CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 1, target_section_shift); } AddByte(value); struct EvalContext etx; SetEvalCtxDefaults(etx); etx.pc = CurrSection().GetPC()-2; line.split_token_trim_track_parens(','); error = EvalExpression(line, etx, value); if (error==STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], line, source_file, LateEval::LET_BYTE); AddByte(value); break; } case CA_BRANCH: if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BRANCH); else if (((int)value - (int)CurrSection().GetPC()-1) < -128 || ((int)value - (int)CurrSection().GetPC()-1) > 127) error = ERROR_BRANCH_OUT_OF_RANGE; AddByte(evalLater ? 0 : (uint8_t)((int)value - (int)CurrSection().GetPC()) - 1); break; case CA_BRANCH_16: if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BRANCH_16); AddWord(evalLater ? 0 : (value-(CurrSection().GetPC()+2))); break; case CA_BYTE_BRANCH: { if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE); else if (error == STATUS_RELATIVE_SECTION) CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 1, target_section_shift); AddByte(value); struct EvalContext etx; SetEvalCtxDefaults(etx); etx.pc = CurrSection().GetPC()-2; etx.relative_section = SectionId(); error = EvalExpression(line, etx, value); if (error==STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], line, source_file, LateEval::LET_BRANCH); else if (((int)value - (int)CurrSection().GetPC() - 1) < -128 || ((int)value - (int)CurrSection().GetPC() - 1) > 127) error = ERROR_BRANCH_OUT_OF_RANGE; AddByte((error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) ? 0 : (uint8_t)((int)value - (int)CurrSection().GetPC()) - 1); break; } case CA_NONE: break; } } return error; } // Build a line of code void Asm::PrintError(strref line, StatusCode error, strref source) { strown<512> errorText; if (contextStack.has_work()) { errorText.sprintf("Error " STRREF_FMT "(%d): ", STRREF_ARG(contextStack.curr().source_name), contextStack.curr().source_file.count_lines(line)+1); } else if (source) { errorText.sprintf_append("Error (%d): ", source.count_lines(line)); } else { errorText.append("Error: "); } errorText.append(aStatusStrings[error]); errorText.append(" \""); errorText.append(line.get_trimmed_ws()); errorText.append("\"\n"); errorText.c_str(); fwrite(errorText.get(), errorText.get_len(), 1, stderr); error_encountered = true; } // Build a line of code StatusCode Asm::BuildLine(strref line) { StatusCode error = STATUS_OK; // MERLIN: First char of line is * means comment if (Merlin()&&line[0]=='*') { return STATUS_OK; } // remember for listing int start_section = SectionId(); int start_address = CurrSection().address; strref code_line = line; list_flags = 0; while (line && error == STATUS_OK) { strref line_start = line; char char0 = line[0]; // first char including white space line.skip_whitespace(); // skip to first character line = line.before_or_full(';'); // clip any line comments line = line.before_or_full(c_comment); line.clip_trailing_whitespace(); if (KickAsm()&&line.get_first()==':') { ++line; } // Kick Assembler macro prefix (incompatible with merlin and sane syntax) strref line_nocom = line; strref operation = line.split_range(Merlin() ? label_end_char_range_merlin : label_end_char_range); if( operation.get_last() == ':' ) { operation.clip( 1 ); } char char1 = operation[0]; // first char of first word char charE = operation.get_last(); // end char of first word line.trim_whitespace(); bool force_label = charE==':' || charE=='$'; if (!force_label && Merlin()&&(line||operation)) { // MERLIN fixes and PoP does some naughty stuff like 'and = 0' force_label = (!strref::is_ws(char0)&&char0!='{' && char0!='}')||char1==']'||charE=='?'; } /*else if (!Merlin()&&line[0]==':') { force_label = true; }*/ if (!operation && !force_label) { if (ConditionalAsm()) { // scope open / close switch (line[0]) { case '{': error = EnterScope(); brace_depth++; list_flags |= ListLine::CYCLES_START; if (error == STATUS_OK) { ++line; line.skip_whitespace(); } break; case '}': // check for late eval of anything with an end scope error = ExitScope(); for (LabelPool *pool = labelPools.getValues(), *end = pool+labelPools.count(); pool!=end; pool++) { pool->ExitScope((uint16_t)brace_depth); } brace_depth--; list_flags |= ListLine::CYCLES_STOP; if (error == STATUS_OK) { ++line; line.skip_whitespace(); } break; case '*': // if first char is '*' this seems like a line comment on some assemblers line.clear(); break; case 127: ++line; // bad character? break; } } else { line.clear(); } } else { // ignore leading period for instructions and directives - not for labels strref label = operation; if ((!Merlin()&&operation[0]==':')||operation[0]=='.') { ++operation; } operation = operation.before_or_full('.'); int op_idx = LookupOpCodeIndex(operation.fnv1a_lower(), aInstructions, num_instructions); if (op_idx >= 0 && !force_label && (aInstructions[op_idx].type==OT_DIRECTIVE || line[0]!='=')) { if (line_nocom.is_substr(operation.get())) { line = line_nocom + strl_t(operation.get()+operation.get_len()-line_nocom.get()); line.skip_whitespace(); } if (aInstructions[op_idx].type==OT_DIRECTIVE) { error = ApplyDirective((AssemblerDirective)aInstructions[op_idx].index, line, contextStack.curr().source_file); list_flags |= ListLine::KEYWORD; } else if (ConditionalAsm() && aInstructions[op_idx].type == OT_MNEMONIC) { error = AddOpcode(line, aInstructions[op_idx].index, contextStack.curr().source_file); list_flags |= ListLine::MNEMONIC; } line.clear(); } else if (!ConditionalAsm()) { line.clear(); // do nothing if conditional nesting so clear the current line } else if (line.get_first()=='=') { ++line; error = AssignLabel(label, line); line.clear(); list_flags |= ListLine::KEYWORD; } else if (keyword_equ.is_prefix_word(line)) { line += keyword_equ.get_len(); line.skip_whitespace(); error = AssignLabel(label, line); line.clear(); list_flags |= ListLine::KEYWORD; } else { uint32_t nameHash = label.fnv1a(); uint32_t macro = FindLabelIndex(nameHash, macros.getKeys(), macros.count()); bool gotConstruct = false; while (macro < macros.count() && nameHash==macros.getKey(macro)) { if (macros.getValue(macro).name.same_str_case(label)) { error = BuildMacro(macros.getValue(macro), line); gotConstruct = true; line.clear(); // don't process codes from here break; } macro++; } if (!gotConstruct) { uint32_t labPool = FindLabelIndex(nameHash, labelPools.getKeys(), labelPools.count()); gotConstruct = false; while (labPool < labelPools.count() && nameHash==labelPools.getKey(labPool)) { if (labelPools.getValue(labPool).pool_name.same_str_case(label)) { error = AssignPoolLabel(labelPools.getValue(labPool), line); gotConstruct = true; line.clear(); // don't process codes from here break; } labPool++; } if (!gotConstruct) { if (StringSymbol *pStr = GetString(label)) { StringAction(pStr, line); line.clear(); } else if (Merlin() && strref::is_ws(line_start[0])) { error = ERROR_UNDEFINED_CODE; } else if (label[0]=='$') { line.clear(); } else { if (label.get_last()==':') { label.clip(1); } error = AddressLabel(label); line = line_start + int(label.get() + label.get_len() -line_start.get()); if (line[0]==':'||line[0]=='?') { ++line; } // there may be codes after the label list_flags |= ListLine::KEYWORD; } } } } } if( error >= ERROR_STOP_PROCESSING_ON_HIGHER ) { return error; } // Check for unterminated condition in source if (!contextStack.curr().next_source && (!ConditionalAsm() || ConditionalConsumed() || !conditional_depth)) { if (Merlin()) { // this isn't a listed feature, conditional_nesting[0] = 0; // some files just seem to get away without closing conditional_consumed[0] = 0; conditional_depth = 0; } else { PrintError(conditional_source[conditional_depth], ERROR_UNTERMINATED_CONDITION); return ERROR_UNTERMINATED_CONDITION; } } if (line.same_str_case(line_start)) { error = ERROR_UNABLE_TO_PROCESS; } else if (CurrSection().type==ST_ZEROPAGE && CurrSection().address>0x100) { error = ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE; } if (error>STATUS_XREF_DEPENDENT) { PrintError(line_start, error); } // dealt with error, continue with next instruction unless too broken if (error<ERROR_STOP_PROCESSING_ON_HIGHER) { error = STATUS_OK; } } // update listing if (error == STATUS_OK && list_assembly) { if (SectionId() == start_section) { Section &curr = CurrSection(); if (!curr.pListing) { curr.pListing = new Listing; } if (curr.pListing && curr.pListing->size()==curr.pListing->capacity()) { curr.pListing->reserve(curr.pListing->size()+256); } if (((list_flags&(ListLine::KEYWORD|ListLine::CYCLES_START|ListLine::CYCLES_STOP)) || (curr.address != start_address && curr.size())) && !curr.IsDummySection()) { struct ListLine lst; lst.address = start_address - curr.start_address; lst.size = curr.address - start_address; lst.code = contextStack.curr().source_file; lst.source_name = contextStack.curr().source_name; lst.line_offs = int(code_line.get() - lst.code.get()); lst.flags = list_flags; curr.pListing->push_back(lst); } } } return error; } // Build a segment of code (file or macro) StatusCode Asm::BuildSegment() { StatusCode error = STATUS_OK; while (contextStack.curr().read_source) { contextStack.curr().next_source = contextStack.curr().read_source; error = BuildLine(contextStack.curr().next_source.line()); if (error>ERROR_STOP_PROCESSING_ON_HIGHER) { break; } contextStack.curr().read_source = contextStack.curr().next_source; } if (error == STATUS_OK) { error = CheckLateEval(strref(), CurrSection().GetPC()); } return error; } // Produce the assembler listing #define MAX_DEPTH_CYCLE_COUNTER 64 struct cycleCnt { int base; int16_t plus, a16, x16, dp; void clr() { base = 0; plus = a16 = x16 = dp = 0; } void add(uint8_t c) { if (c != 0xff) { base += (c >> 1) & 7; plus += c & 1; if (c & 0xf0) { int i = c >> 4; if (i <= 8) { a16 += timing_65816_plus[i][0]; x16 += timing_65816_plus[i][1]; dp += timing_65816_plus[i][2]; } } } } int plus_acc() { return plus + a16 + x16 + dp; } void combine(const struct cycleCnt &o) { base += o.base; plus += o.plus; a16 += o.a16; x16 += o.x16; dp += o.dp; } bool complex() const { return a16 != 0 || x16 != 0 || dp != 0; } static int get_base(uint8_t c) { return (c & 0xf) >> 1; } static int sum_plus(uint8_t c) { if (c==0xff) { return 0; } int i = c >> 4; if (i) { return i<=8 ? (timing_65816_plus[i][0]+timing_65816_plus[i][1]+timing_65816_plus[i][2]) : 0; } return c & 1; } }; bool Asm::ListTassStyle( strref filename ) { // NOTE: This file needs more information to be useful for things FILE *f = stdout; bool opened = false; if( filename ) { f = fopen( strown<512>( filename ).c_str(), "w" ); if( !f ) { return false; } opened = true; } // ensure that the greatest instruction set referenced is used for listing if (list_cpu!=cpu) { SetCPU(list_cpu); } // Build a disassembly lookup table uint8_t mnemonic[256]; uint8_t addrmode[256]; memset(mnemonic, 255, sizeof(mnemonic)); memset(addrmode, 255, sizeof(addrmode)); for (int i = 0; i < opcode_count; i++) { for (int j = AMB_COUNT-1; j >= 0; j--) { if (opcode_table[i].modes & (1 << j)) { uint8_t op = opcode_table[i].aCodes[j]; if (addrmode[op]==255) { mnemonic[op] = (uint8_t)i; addrmode[op] = (uint8_t)j; } } } } strref first_src; for( std::vector<Section>::iterator si = allSections.begin(); si != allSections.end() && !first_src; ++si ) { if( !si->pListing ) continue; for(auto & li : *si->pListing) { if( li.source_name ) { first_src = li.source_name; break; } } } fprintf( f, ";6502/65C02/65816/CPU64/DTV Turbo Assembler V1.4x listing file of \"" STRREF_FMT "\"\n;done on WkDay Month Date Time\n\n", STRREF_ARG( first_src ) ); fprintf( f, ";Offset\t;Hex\t\t;Monitor\t;Source\n\n" ); for(auto &si : allSections) { if( !si.pListing || si.type == ST_REMOVED || !si.pListing ) { continue; } for(const struct ListLine &lst : *si.pListing) { /*if( lst.size && lst.wasMnemonic() )*/ { strown<256> out; /*if( lst.size )*/ { out.sprintf_append( ".%04x\t", lst.address + si.start_address ); } int s = lst.wasMnemonic() ? ( lst.size < 4 ? lst.size : 4 ) : ( lst.size < 8 ? lst.size : 8 ); if( si.output && si.output_capacity >= size_t( lst.address + s ) ) { for( int b = 0; b < s; ++b ) { out.sprintf_append( "%02x ", si.output[ lst.address + b ] ); } } out.append( "\t\t" ); if (lst.size && lst.wasMnemonic()) { //out.pad_to(' ', 21); uint8_t *buf = si.output + lst.address; uint8_t op = mnemonic[*buf]; uint8_t am = addrmode[*buf]; if (op != 255 && am != 255 && am<(sizeof(aAddrModeFmt)/sizeof(aAddrModeFmt[0]))) { const char *fmt = aAddrModeFmt[am]; if (opcode_table[op].modes & AMM_FLIPXY) { if (am == AMB_ZP_X) fmt = "%s $%02x,y"; else if (am == AMB_ABS_X) fmt = "%s $%04x,y"; } if (opcode_table[op].modes & AMM_ZP_ABS) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1], (char)buf[2]+lst.address+si.start_address+3); } else if (opcode_table[op].modes & AMM_BRANCH) { out.sprintf_append(fmt, opcode_table[op].instr, (char)buf[1]+lst.address+si.start_address+2); } else if (opcode_table[op].modes & AMM_BRANCH_L) { out.sprintf_append(fmt, opcode_table[op].instr, (int16_t)(buf[1]|(buf[2]<<8))+lst.address+si.start_address+3); } else if (am==AMB_NON||am==AMB_ACC) { out.sprintf_append(fmt, opcode_table[op].instr); } else if (am==AMB_ABS||am==AMB_ABS_X||am==AMB_ABS_Y||am==AMB_REL||am==AMB_REL_X||am==AMB_REL_L) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1]|(buf[2]<<8)); } else if (am==AMB_ABS_L||am==AMB_ABS_L_X) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1]|(buf[2]<<8)|(buf[3]<<16)); } else if (am==AMB_BLK_MOV) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1], buf[2]); } else if (am==AMB_IMM && lst.size==3) { out.sprintf_append("%s #$%04x", opcode_table[op].instr, buf[1]|(buf[2]<<8)); } else { out.sprintf_append(fmt, opcode_table[op].instr, buf[1]); } } } else { out.append('\t');} // out.pad_to( ' ', 36 ); out.append( '\t' ); strref line = lst.code.get_skipped( lst.line_offs ).get_line(); line.clip_trailing_whitespace(); strown<128> line_fix( line ); for( strl_t pos = 0; pos < line_fix.len(); ++pos ) { if( line_fix[ pos ] == '\t' ) line_fix.exchange( pos, 1, pos & 1 ? strref( " " ) : strref( " " ) ); } out.append( line_fix.get_strref() ); fprintf( f, STRREF_FMT "\n", STRREF_ARG( out ) ); } } } fprintf( f, "\n;****** end of code\n" ); if( opened ) { fclose( f ); } return true; } bool Asm::List(strref filename) { FILE *f = stdout; bool opened = false; if (filename) { f = fopen(strown<512>(filename).c_str(), "w"); if (!f) { return false; } opened = true; } // ensure that the greatest instruction set referenced is used for listing if (list_cpu!=cpu) { SetCPU(list_cpu); } // Build a disassembly lookup table uint8_t mnemonic[256]; uint8_t addrmode[256]; memset(mnemonic, 255, sizeof(mnemonic)); memset(addrmode, 255, sizeof(addrmode)); for (int i = 0; i < opcode_count; i++) { for (int j = AMB_COUNT-1; j >= 0; j--) { if (opcode_table[i].modes & (1 << j)) { uint8_t op = opcode_table[i].aCodes[j]; if (addrmode[op]==255) { mnemonic[op] = (uint8_t)i; addrmode[op] = (uint8_t)j; } } } } struct cycleCnt cycles[MAX_DEPTH_CYCLE_COUNTER]; int16_t cycles_depth = 0; memset(cycles, 0, sizeof(cycles)); // show merged sections for (size_t i = 0, n = allSections.size(); i < n; ++i) { Section& s = allSections[i]; if (s.type != ST_REMOVED) { if (s.include_from) { fprintf(f, "Section " STRREF_FMT " from " STRREF_FMT " $%04x - $%04x ($%04x)\n", STRREF_ARG(s.name), STRREF_ARG(s.include_from), s.start_address, s.address, s.address - s.start_address); } else { fprintf(f, "Section " STRREF_FMT " $%04x - $%04x ($%04x)\n", STRREF_ARG(s.name), s.start_address, s.address, s.address - s.start_address); } for (size_t j = 0; j < n; ++j) { Section& s2 = allSections[j]; if (s2.type == ST_REMOVED && s2.merged_into >= 0) { int offset = s2.merged_at; int parent = s2.merged_into; while (parent != i && parent >= 0) { offset += allSections[parent].merged_at; parent = allSections[parent].merged_into; } if (parent == i) { if (s2.include_from) { fprintf(f, " + " STRREF_FMT " from " STRREF_FMT " $%04x - $%04x ($%04x) at offset 0x%04x\n", STRREF_ARG(s2.name), STRREF_ARG(s2.include_from), s2.merged_at + s.start_address, s2.merged_at + s.start_address + s2.merged_size, s2.merged_size, s2.merged_at); } else { fprintf(f, " + " STRREF_FMT " $%04x - $%04x ($%04x) at offset 0x%04x\n", STRREF_ARG(s2.name), s2.merged_at + s.start_address, s2.merged_at + s.start_address + s2.merged_size, s2.merged_size, s2.merged_at); } } } } } } strref prev_src; int prev_offs = 0; for (std::vector<Section>::iterator si = allSections.begin(); si != allSections.end(); ++si) { if (si->merged_into >= 0) { fprintf(f, STRREF_FMT " from " STRREF_FMT " merged into " STRREF_FMT " at offset 0x%04x\n", STRREF_ARG(si->name), STRREF_ARG(si->include_from), STRREF_ARG(allSections[si->merged_into].name), si->merged_at); } if (si->type==ST_REMOVED) { continue; } if (si->address_assigned) fprintf(f, "Section " STRREF_FMT " (%d, %s): $%04x-$%04x\n", STRREF_ARG(si->name), (int)(&*si - &allSections[0]), si->type>=0 && si->type<num_section_type_str ? str_section_type[si->type] : "???", si->start_address, si->address); else fprintf(f, "Section " STRREF_FMT " (%d, %s) (relocatable)\n", STRREF_ARG(si->name), (int)(&*si - &allSections[0]), str_section_type[si->type]); if (!si->pListing) continue; for (Listing::iterator li = si->pListing->begin(); li != si->pListing->end(); ++li) { strown<256> out; const struct ListLine &lst = *li; if (prev_src.fnv1a() != lst.source_name.fnv1a() || lst.line_offs < prev_offs) { fprintf(f, STRREF_FMT "(%d):\n", STRREF_ARG(lst.source_name), lst.code.count_lines(lst.line_offs)); prev_src = lst.source_name; } else { strref prvline = lst.code.get_substr(prev_offs, lst.line_offs - prev_offs); prvline.next_line(); if (prvline.count_lines() < 5) { while (strref space_line = prvline.line()) { space_line.clip_trailing_whitespace(); strown<128> line_fix(space_line); for (strl_t pos = 0; pos < line_fix.len(); ++pos) { if (line_fix[pos]=='\t') { line_fix.exchange(pos, 1, pos&1 ? strref(" ") : strref(" ")); } } out.pad_to(' ', aCPUs[cpu].timing ? 40 : 33); out.append(line_fix.get_strref()); fprintf(f, STRREF_FMT "\n", STRREF_ARG(out)); out.clear(); } } else { fprintf(f, STRREF_FMT "(%d):\n", STRREF_ARG(lst.source_name), lst.code.count_lines(lst.line_offs)); } } if (lst.size) { out.sprintf_append("$%04x ", lst.address+si->start_address); } int s = lst.wasMnemonic() ? (lst.size < 4 ? lst.size : 4) : (lst.size < 8 ? lst.size : 8); if (si->output && si->output_capacity >= size_t(lst.address + s)) { for (int b = 0; b<s; ++b) { out.sprintf_append("%02x ", si->output[lst.address+b]); } } if (lst.startClock() && cycles_depth<MAX_DEPTH_CYCLE_COUNTER) { cycles_depth++; cycles[cycles_depth].clr(); out.pad_to(' ', 6); out.sprintf_append("c>%d", cycles_depth); } if (lst.stopClock()) { out.pad_to(' ', 6); if (cycles[cycles_depth].complex()) { out.sprintf_append("c<%d = %d + m%d + i%d + d%d", cycles_depth, cycles[cycles_depth].base, cycles[cycles_depth].a16, cycles[cycles_depth].x16, cycles[cycles_depth].dp); } else { out.sprintf_append("c<%d = %d + %d", cycles_depth, cycles[cycles_depth].base, cycles[cycles_depth].plus_acc()); } if (cycles_depth) { cycles_depth--; cycles[cycles_depth].combine(cycles[cycles_depth + 1]); } } if (lst.size && lst.wasMnemonic()) { out.pad_to(' ', 18); uint8_t *buf = si->output + lst.address; uint8_t op = mnemonic[*buf]; uint8_t am = addrmode[*buf]; if (op != 255 && am != 255 && am<(sizeof(aAddrModeFmt)/sizeof(aAddrModeFmt[0]))) { const char *fmt = aAddrModeFmt[am]; if (opcode_table[op].modes & AMM_FLIPXY) { if (am == AMB_ZP_X) fmt = "%s $%02x,y"; else if (am == AMB_ABS_X) fmt = "%s $%04x,y"; } if (opcode_table[op].modes & AMM_ZP_ABS) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1], (char)buf[2]+lst.address+si->start_address+3); } else if (opcode_table[op].modes & AMM_BRANCH) { out.sprintf_append(fmt, opcode_table[op].instr, (char)buf[1]+lst.address+si->start_address+2); } else if (opcode_table[op].modes & AMM_BRANCH_L) { out.sprintf_append(fmt, opcode_table[op].instr, (int16_t)(buf[1]|(buf[2]<<8))+lst.address+si->start_address+3); } else if (am==AMB_NON||am==AMB_ACC) { out.sprintf_append(fmt, opcode_table[op].instr); } else if (am==AMB_ABS||am==AMB_ABS_X||am==AMB_ABS_Y||am==AMB_REL||am==AMB_REL_X||am==AMB_REL_L) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1]|(buf[2]<<8)); } else if (am==AMB_ABS_L||am==AMB_ABS_L_X) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1]|(buf[2]<<8)|(buf[3]<<16)); } else if (am==AMB_BLK_MOV) { out.sprintf_append(fmt, opcode_table[op].instr, buf[1], buf[2]); } else if (am==AMB_IMM && lst.size==3) { out.sprintf_append("%s #$%04x", opcode_table[op].instr, buf[1]|(buf[2]<<8)); } else { out.sprintf_append(fmt, opcode_table[op].instr, buf[1]); } if (aCPUs[cpu].timing) { cycles[cycles_depth].add(aCPUs[cpu].timing[*buf]); out.pad_to(' ', 33); if (cycleCnt::sum_plus(aCPUs[cpu].timing[*buf])==1) { out.sprintf_append("%d+", cycleCnt::get_base(aCPUs[cpu].timing[*buf])); } else if (cycleCnt::sum_plus(aCPUs[cpu].timing[*buf])) { out.sprintf_append("%d+%d", cycleCnt::get_base(aCPUs[cpu].timing[*buf]), cycleCnt::sum_plus(aCPUs[cpu].timing[*buf])); } else { out.sprintf_append("%d", cycleCnt::get_base(aCPUs[cpu].timing[*buf])); } } } } out.pad_to(' ', aCPUs[cpu].timing ? 40 : 33); strref line = lst.code.get_skipped(lst.line_offs).get_line(); line.clip_trailing_whitespace(); strown<128> line_fix(line); for (strl_t pos = 0; pos < line_fix.len(); ++pos) { if (line_fix[pos] == '\t') line_fix.exchange(pos, 1, pos & 1 ? strref(" ") : strref(" ")); } out.append(line_fix.get_strref()); fprintf(f, STRREF_FMT "\n", STRREF_ARG(out)); prev_offs = lst.line_offs; } } if (opened) { fclose(f); } return true; } // Create a listing of all valid instructions and addressing modes bool Asm::AllOpcodes(strref filename) { FILE *f = stdout; bool opened = false; if (filename) { f = fopen(strown<512>(filename).c_str(), "w"); if (!f) { return false; } opened = true; } for (int i = 0; i < opcode_count; i++) { uint32_t modes = opcode_table[i].modes; for (int a = 0; a < AMB_COUNT; a++) { if (modes & (1<<a)) { const char *fmt = aAddrModeFmt[a]; fputs("\t", f); if (opcode_table[i].modes & AMM_BRANCH) { fprintf(f, "%s *+%d", opcode_table[i].instr, 5); } else if (a==AMB_ZP_ABS) { fprintf(f, "%s $%02x,*+%d", opcode_table[i].instr, 0x23, 13); } else { if (opcode_table[i].modes & AMM_FLIPXY) { if (a == AMB_ZP_X) fmt = "%s $%02x,y"; else if (a == AMB_ABS_X) fmt = "%s $%04x,y"; } if (a == AMB_ABS_L || a == AMB_ABS_L_X) { if ((modes & ~(AMM_ABS_L|AMM_ABS_L_X))) { fprintf(f, a==AMB_ABS_L ? "%s.l $%06x" : "%s.l $%06x,x", opcode_table[i].instr, 0x222120); } else { fprintf(f, fmt, opcode_table[i].instr, 0x222120); } } else if (a==AMB_ABS||a==AMB_ABS_X||a==AMB_ABS_Y||a==AMB_REL||a==AMB_REL_X||a==AMB_REL_L) { fprintf(f, fmt, opcode_table[i].instr, 0x2120); } else if (a==AMB_IMM&&(modes&(AMM_IMM_DBL_A|AMM_IMM_DBL_XY))) { fprintf(f, "%s.b #$%02x\n", opcode_table[i].instr, 0x21); fprintf(f, "\t%s.w #$%04x", opcode_table[i].instr, 0x2322); } else { fprintf(f, fmt, opcode_table[i].instr, 0x21, 0x20, 0x1f); } } fputs("\n", f); } } } if (opened) { fclose(f); } return true; } // create an instruction table (mnemonic hash lookup + directives) void Asm::Assemble(strref source, strref filename, bool obj_target) { SetCPU(cpu); StatusCode error = STATUS_OK; PushContext(filename, source, source); scope_address[scope_depth] = CurrSection().GetPC(); while (contextStack.has_work() && error == STATUS_OK) { error = BuildSegment(); if( error >= ERROR_STOP_PROCESSING_ON_HIGHER ) { break; } if (contextStack.curr().complete()) { error = PopContext(); } else { error = FlushLocalLabels(scope_depth); if (error>=FIRST_ERROR) { break; } contextStack.curr().restart(); error = STATUS_OK; } } if (error==STATUS_OK) { if (!obj_target) { LinkZP(); } error = CheckLateEval(); if (error>STATUS_XREF_DEPENDENT) { strown<512> errorText; errorText.copy("Error: "); errorText.append(aStatusStrings[error]); fwrite(errorText.get(), errorText.get_len(), 1, stderr); } else { CheckLateEval(strref(), -1, true); } // output any missing xref's if (!obj_target) { for (std::vector<LateEval>::iterator i = lateEval.begin(); i!=lateEval.end(); ++i) { strown<512> errorText; int line = i->source_file.count_lines(i->expression); errorText.sprintf("Error (%d): ", line+1); errorText.append("Failed to evaluate label \""); errorText.append(i->expression); if (line>=0) { errorText.append("\" : \""); errorText.append(i->source_file.get_line(line).get_trimmed_ws()); } errorText.append("\"\n"); fwrite(errorText.get(), errorText.get_len(), 1, stderr); } } } else { PrintError(&contextStack.curr() ? contextStack.curr().read_source.get_line() : strref(), error); } } // // // OBJECT FILE HANDLING // // struct ObjFileHeader { int16_t id; // 'x6' int16_t sections; int16_t relocs; int16_t labels; int16_t late_evals; int16_t map_symbols; uint32_t stringdata; int bindata; }; struct ObjFileStr { int offs; // offset into string table }; struct ObjFileSection { enum SectionFlags { OFS_DUMMY, OFS_FIXED, OFS_MERGED, }; struct ObjFileStr name; struct ObjFileStr exp_app; int start_address; int end_address; // address size int output_size; // assembled binary size int align_address; int16_t next_group; // next section of group int16_t first_group; // first section of group int16_t relocs; SectionType type; int8_t flags; }; struct ObjFileReloc { int base_value; int section_offset; int16_t target_section; int8_t bytes; int8_t shift; }; struct ObjFileLabel { enum LabelFlags { OFL_EVAL = (1<<15), // Evaluated (may still be relative) OFL_ADDR = (1<<14), // Address or Assign OFL_CNST = (1<<13), // Constant OFL_XDEF = OFL_CNST-1 // External (index into file array) }; struct ObjFileStr name; int value; int flags; // 1<<(LabelFlags) int16_t section; // -1 if resolved, file section # if section rel int16_t mapIndex; // -1 if resolved, index into map if relative }; struct ObjFileLateEval { struct ObjFileStr label; struct ObjFileStr expression; int address; // PC relative to section or fixed int target; // offset into section memory int16_t section; // section to target int16_t rept; // value of rept for this late eval int16_t scope; // PC start of scope int16_t type; // label, byte, branch, word (LateEval::Type) }; struct ObjFileMapSymbol { struct ObjFileStr name; // symbol name int value; int16_t section; bool local; // local labels are probably needed }; // Simple string pool, converts strref strings to zero terminated strings and returns the offset to the string in the pool. static int _AddStrPool(const strref str, pairArray<uint32_t, int> *pLookup, char **strPool, uint32_t &strPoolSize, uint32_t &strPoolCap) { if (!str.get()||!str.get_len()) { return -1; } // empty string uint32_t hash = str.fnv1a(); uint32_t index = FindLabelIndex(hash, pLookup->getKeys(), pLookup->count()); if (index<pLookup->count()&&str.same_str_case(*strPool+pLookup->getValue(index))) { return pLookup->getValue(index); } int strOffs = strPoolSize; if ((strOffs + str.get_len() + 1) > strPoolCap) { strPoolCap = strOffs + (uint32_t)str.get_len() + 4096; char *strPoolGrow = (char*)malloc(strPoolCap); if (strPoolGrow) { if (*strPool) { memcpy(strPoolGrow, *strPool, strPoolSize); free(*strPool); } *strPool = strPoolGrow; } else { return -1; } } if (*strPool) { char *dest = *strPool + strPoolSize; memcpy(dest, str.get(), str.get_len()); dest[str.get_len()] = 0; strPoolSize += (uint32_t)str.get_len()+1; pLookup->insert(index, hash); pLookup->getValues()[index] = strOffs; } return strOffs; } StatusCode Asm::WriteObjectFile(strref filename) { if (allSections.size()==0) return ERROR_NOT_A_SECTION; if (FILE *f = fopen(strown<512>(filename).c_str(), "wb")) { struct ObjFileHeader hdr = { 0 }; hdr.id = 0x7836; hdr.sections = (int16_t)allSections.size(); hdr.relocs = 0; hdr.bindata = 0; for (std::vector<Section>::iterator s = allSections.begin(); s!=allSections.end(); ++s) { if (s->type != ST_REMOVED) { if (s->pRelocs) { hdr.relocs += int16_t(s->pRelocs->size()); } hdr.bindata += s->size(); } } hdr.late_evals = (int16_t)lateEval.size(); hdr.map_symbols = (int16_t)map.size(); hdr.stringdata = 0; // labels don't include XREF labels hdr.labels = 0; for (uint32_t l = 0; l<labels.count(); l++) { if (!labels.getValue(l).reference) { hdr.labels++; } } int16_t *aRemapSects = hdr.sections ? (int16_t*)malloc(sizeof(int16_t) * hdr.sections) : nullptr; if (!aRemapSects) { fclose(f); return ERROR_OUT_OF_MEMORY; } // include space for external protected labels for (std::vector<ExtLabels>::iterator el = externals.begin(); el!=externals.end(); ++el) { hdr.labels += (int16_t)el->labels.count(); } char *stringPool = nullptr; uint32_t stringPoolCap = 0; pairArray<uint32_t, int> stringArray; stringArray.reserve(hdr.labels * 2 + hdr.sections + hdr.late_evals*2); struct ObjFileSection *aSects = hdr.sections ? (struct ObjFileSection*)calloc(hdr.sections, sizeof(struct ObjFileSection)) : nullptr; struct ObjFileReloc *aRelocs = hdr.relocs ? (struct ObjFileReloc*)calloc(hdr.relocs, sizeof(struct ObjFileReloc)) : nullptr; struct ObjFileLabel *aLabels = hdr.labels ? (struct ObjFileLabel*)calloc(hdr.labels, sizeof(struct ObjFileLabel)) : nullptr; struct ObjFileLateEval *aLateEvals = hdr.late_evals ? (struct ObjFileLateEval*)calloc(hdr.late_evals, sizeof(struct ObjFileLateEval)) : nullptr; struct ObjFileMapSymbol *aMapSyms = hdr.map_symbols ? (struct ObjFileMapSymbol*)calloc(hdr.map_symbols, sizeof(struct ObjFileMapSymbol)) : nullptr; int sect = 0, reloc = 0, labs = 0, late = 0, map_sym = 0; memset(aRemapSects, 0xff, sizeof(int16_t) * hdr.sections); // discard the removed sections by making a table of skipped indices for (std::vector<Section>::iterator si = allSections.begin(); si!=allSections.end(); ++si) { if (si->type != ST_REMOVED) aRemapSects[&*si-&allSections[0]] = (int16_t)sect++; } sect = 0; // write out sections and relocs if (hdr.sections) { for (std::vector<Section>::iterator si = allSections.begin(); si!=allSections.end(); ++si) { if (si->type == ST_REMOVED) continue; struct ObjFileSection &s = aSects[sect++]; s.name.offs = _AddStrPool(si->name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); s.exp_app.offs = _AddStrPool(si->export_append, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); s.output_size = si->size(); s.align_address = si->align_address; s.next_group = si->next_group >= 0 ? aRemapSects[si->next_group] : -1; s.first_group = si->first_group >= 0 ? aRemapSects[si->first_group] : -1; s.relocs = si->pRelocs ? (int16_t)(si->pRelocs->size()) : 0; s.start_address = si->start_address; s.end_address = si->address; s.type = si->type; s.flags = (si->IsDummySection() ? (1 << ObjFileSection::OFS_DUMMY) : 0) | (si->IsMergedSection() ? (1 << ObjFileSection::OFS_MERGED) : 0) | (si->address_assigned ? (1 << ObjFileSection::OFS_FIXED) : 0); if (si->pRelocs && si->pRelocs->size() && aRelocs) { for (relocList::iterator ri = si->pRelocs->begin(); ri!=si->pRelocs->end(); ++ri) { struct ObjFileReloc &r = aRelocs[reloc++]; r.base_value = ri->base_value; r.section_offset = ri->section_offset; r.target_section = ri->target_section >= 0 ? aRemapSects[ri->target_section] : -1; r.bytes = ri->bytes; r.shift = ri->shift; } } } } hdr.sections = (int16_t)sect; // write out labels if (hdr.labels) { for (uint32_t li = 0; li<labels.count(); li++) { Label &lo = labels.getValue(li); if (!lo.reference) { struct ObjFileLabel &l = aLabels[labs++]; l.name.offs = _AddStrPool(lo.label_name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); l.value = lo.value; l.section = lo.section >=0 ? aRemapSects[lo.section] : -1; l.mapIndex = (int16_t)lo.mapIndex; l.flags = (lo.constant ? ObjFileLabel::OFL_CNST : 0) | (lo.pc_relative ? ObjFileLabel::OFL_ADDR : 0) | (lo.evaluated ? ObjFileLabel::OFL_EVAL : 0) | (lo.external ? ObjFileLabel::OFL_XDEF : 0); } } } // protected labels included from other object files if (hdr.labels) { int file_index = 1; for (std::vector<ExtLabels>::iterator el = externals.begin(); el != externals.end(); ++el) { for (uint32_t li = 0; li < el->labels.count(); ++li) { Label &lo = el->labels.getValue(li); struct ObjFileLabel &l = aLabels[labs++]; l.name.offs = _AddStrPool(lo.label_name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); l.value = lo.value; l.section = lo.section >= 0 ? aRemapSects[lo.section] : -1; l.mapIndex = (int16_t)lo.mapIndex; l.flags = (lo.constant ? ObjFileLabel::OFL_CNST : 0) | (lo.pc_relative ? ObjFileLabel::OFL_ADDR : 0) | (lo.evaluated ? ObjFileLabel::OFL_EVAL : 0) | file_index; } file_index++; } } // write out late evals if (aLateEvals) { for (std::vector<LateEval>::iterator lei = lateEval.begin(); lei != lateEval.end(); ++lei) { struct ObjFileLateEval &le = aLateEvals[late++]; le.label.offs = _AddStrPool(lei->label, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); le.expression.offs = _AddStrPool(lei->expression, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); le.section = lei->section >= 0 ? aRemapSects[lei->section] : -1; le.rept = lei->rept; le.target = (int16_t)lei->target; le.address = lei->address; le.scope = (int16_t)lei->scope; le.type = (int16_t)lei->type; } } // write out map symbols if (aMapSyms) { for (MapSymbolArray::iterator mi = map.begin(); mi != map.end(); ++mi) { struct ObjFileMapSymbol &ms = aMapSyms[map_sym++]; ms.name.offs = _AddStrPool(mi->name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap); ms.value = mi->value; ms.local = mi->local; ms.section = mi->section >= 0 ? aRemapSects[mi->section] : -1; } } // write out the file fwrite(&hdr, sizeof(hdr), 1, f); fwrite(aSects, sizeof(aSects[0]), sect, f); fwrite(aRelocs, sizeof(aRelocs[0]), reloc, f); fwrite(aLabels, sizeof(aLabels[0]), labs, f); fwrite(aLateEvals, sizeof(aLateEvals[0]), late, f); fwrite(aMapSyms, sizeof(aMapSyms[0]), map_sym, f); fwrite(stringPool, hdr.stringdata, 1, f); for (std::vector<Section>::iterator si = allSections.begin(); si!=allSections.end(); ++si) { if (!si->IsDummySection()&&!si->IsMergedSection()&&si->size()!=0&&si->type!=ST_REMOVED) { fwrite(si->output, si->size(), 1, f); } } // done with I/O fclose(f); if (aRemapSects) { free(aRemapSects); } if (stringPool) { free(stringPool); } if (aMapSyms) { free(aMapSyms); } if (aLateEvals) { free(aLateEvals); } if (aLabels) { free(aLabels); } if (aRelocs) { free(aRelocs); } if (aSects) { free(aSects); } stringArray.clear(); } return STATUS_OK; } StatusCode Asm::ReadObjectFile(strref filename, int link_to_section) { size_t size; strown<512> file; file.copy(filename); // Merlin mostly uses extension-less files, append .x65 as a default if ((Merlin() && !file.has_suffix(".x65")) || filename.find('.')<0) file.append(".x65"); int file_index = (int)externals.size(); if (char *data = LoadBinary(file.get_strref(), size)) { struct ObjFileHeader &hdr = *(struct ObjFileHeader*)data; size_t sum = sizeof(hdr) + hdr.sections*sizeof(struct ObjFileSection) + hdr.relocs * sizeof(struct ObjFileReloc) + hdr.labels * sizeof(struct ObjFileLabel) + hdr.late_evals * sizeof(struct ObjFileLateEval) + hdr.map_symbols * sizeof(struct ObjFileMapSymbol) + hdr.stringdata + hdr.bindata; if (hdr.id == 0x7836 && sum == size) { struct ObjFileSection *aSect = (struct ObjFileSection*)(&hdr + 1); struct ObjFileReloc *aReloc = (struct ObjFileReloc*)(aSect + hdr.sections); struct ObjFileLabel *aLabels = (struct ObjFileLabel*)(aReloc + hdr.relocs); struct ObjFileLateEval *aLateEval = (struct ObjFileLateEval*)(aLabels + hdr.labels); struct ObjFileMapSymbol *aMapSyms = (struct ObjFileMapSymbol*)(aLateEval + hdr.late_evals); const char *str_orig = (const char*)(aMapSyms + hdr.map_symbols); const char *bin_data = str_orig + hdr.stringdata; char *str_pool = (char*)malloc(hdr.stringdata); memcpy(str_pool, str_orig, hdr.stringdata); loadedData.push_back(str_pool); int prevSection = SectionId(); int16_t *aSctRmp = (int16_t*)malloc(hdr.sections * sizeof(int16_t)); int last_linked_section = link_to_section; while (last_linked_section>=0&&allSections[last_linked_section].next_group>=0) { last_linked_section = allSections[last_linked_section].next_group; } // sections for (int si = 0; si < hdr.sections; si++) { int16_t f = aSect[si].flags; if (f & (1 << ObjFileSection::OFS_MERGED)) continue; if (f & (1 << ObjFileSection::OFS_DUMMY)) { if (f&(1 << ObjFileSection::OFS_FIXED)) { DummySection(aSect[si].start_address); CurrSection().AddBin(nullptr, aSect[si].end_address - aSect[si].start_address); } else { DummySection(); CurrSection().AddBin(nullptr, aSect[si].end_address - aSect[si].start_address); } } else { if (f&(1<<ObjFileSection::OFS_FIXED)) { SetSection(aSect[si].name.offs>=0 ? strref(str_pool+aSect[si].name.offs) : strref(), aSect[si].start_address); } else { SetSection(aSect[si].name.offs>=0 ? strref(str_pool+aSect[si].name.offs) : strref()); } Section &s = CurrSection(); s.include_from = filename; s.export_append = aSect[si].exp_app.offs>=0 ? strref(str_pool + aSect[si].name.offs) : strref(); s.align_address = aSect[si].align_address; s.address = aSect[si].end_address; s.type = aSect[si].type; if (aSect[si].output_size) { s.output = (uint8_t*)malloc(aSect[si].output_size); memcpy(s.output, bin_data, aSect[si].output_size); s.curr = s.output + aSect[si].output_size; s.output_capacity = aSect[si].output_size; bin_data += aSect[si].output_size; } if (last_linked_section>=0) { allSections[last_linked_section].next_group = SectionId(); s.first_group = allSections[last_linked_section].first_group >=0 ? allSections[last_linked_section].first_group : last_linked_section; last_linked_section = SectionId(); } } aSctRmp[si] = (int16_t)allSections.size()-1; } // fix up groups and relocs int curr_reloc = 0; for (int si = 0; si < hdr.sections; si++) { Section &s = allSections[aSctRmp[si]]; if (aSect[si].first_group >= 0) s.first_group = aSctRmp[aSect[si].first_group]; if (aSect[si].next_group >= 0) s.first_group = aSctRmp[aSect[si].next_group]; for (int ri = 0; ri < aSect[si].relocs; ri++) { int r = ri + curr_reloc; struct ObjFileReloc &rs = aReloc[r]; allSections[aSctRmp[si]].AddReloc(rs.base_value, rs.section_offset, aSctRmp[rs.target_section], rs.bytes, rs.shift); } curr_reloc += aSect[si].relocs; } for (int mi = 0; mi < hdr.map_symbols; mi++) { struct ObjFileMapSymbol &m = aMapSyms[mi]; if (map.size()==map.capacity()) { map.reserve(map.size()+256); } MapSymbol sym; sym.name = m.name.offs>=0 ? strref(str_pool + m.name.offs) : strref(); sym.section = m.section >=0 ? aSctRmp[m.section] : m.section; sym.value = m.value; sym.local = m.local; map.push_back(sym); } for (int li = 0; li < hdr.labels; li++) { struct ObjFileLabel &l = aLabels[li]; strref name = l.name.offs >= 0 ? strref(str_pool + l.name.offs) : strref(); Label *lbl = GetLabel(name); int16_t f = (int16_t)l.flags; int external = f & ObjFileLabel::OFL_XDEF; if (external == ObjFileLabel::OFL_XDEF) { if (!lbl) { lbl = AddLabel(name.fnv1a()); lbl->referenced = false; } // insert shared label else if (!lbl->reference) { continue; } } else { // insert protected label while ((file_index + external) >= (int)externals.size()) { if (externals.size()==externals.capacity()) { externals.reserve(externals.size()+32); } externals.push_back(ExtLabels()); } uint32_t hash = name.fnv1a(); uint32_t index = FindLabelIndex(hash, externals[file_index].labels.getKeys(), externals[file_index].labels.count()); externals[file_index].labels.insert(index, hash); lbl = externals[file_index].labels.getValues() + index; } lbl->label_name = name; lbl->pool_name.clear(); lbl->value = l.value; lbl->section = l.section >= 0 ? aSctRmp[l.section] : l.section; lbl->mapIndex = l.mapIndex >= 0 ? (l.mapIndex + (int)map.size()) : -1; lbl->evaluated = !!(f & ObjFileLabel::OFL_EVAL); lbl->pc_relative = !!(f & ObjFileLabel::OFL_ADDR); lbl->constant = !!(f & ObjFileLabel::OFL_CNST); lbl->external = external == ObjFileLabel::OFL_XDEF; lbl->reference = false; } // no protected labels => don't track as separate file if (file_index==(int)externals.size()) { file_index = -1; } for (int li = 0; li < hdr.late_evals; ++li) { struct ObjFileLateEval &le = aLateEval[li]; strref name = le.label.offs >= 0 ? strref(str_pool + le.label.offs) : strref(); Label *pLabel = GetLabel(name); if (pLabel) { if (pLabel->evaluated) { AddLateEval(name, le.address, le.scope, strref(str_pool + le.expression.offs), (LateEval::Type)le.type); LateEval &last = lateEval[lateEval.size()-1]; last.section = le.section >= 0 ? aSctRmp[le.section] : le.section; last.rept = le.rept; last.source_file = strref(); last.file_ref = file_index; } } else { AddLateEval(le.target, le.address, le.scope, strref(str_pool + le.expression.offs), strref(), (LateEval::Type)le.type); LateEval &last = lateEval[lateEval.size()-1]; last.section = le.section >= 0 ? aSctRmp[le.section] : le.section; last.rept = le.rept; last.file_ref = file_index; } } free(aSctRmp); // restore previous section current_section = &allSections[prevSection]; } else { return ERROR_NOT_AN_X65_OBJECT_FILE; } } return STATUS_OK; } // number of section types that can be merged enum OMFRecCode { OMFR_END = 0, OMFR_RELOC = 0xe2, // bytes.b, bitshift.b, offset.l, value.l OMFR_INTERSEG = 0xe3, // bytes.b, bitshift.b, offset.l, filenum.w, segnum.w, offsref.l OMFR_LCONST = 0xf2, // bytes.b, b,b,b, data.b[bytes] OMFR_cRELOC = 0xf5, // bytes.b, bitshift.b, offset.w, value.w OMFR_cINTERSEG = 0xf6, // bytes.b, bitshift.b, offset.w, segnum.b, offsref.w OMFR_SUPER = 0xf7, }; struct OMFSegHdr { uint8_t SegTotal[4]; // + Segment Segment Header size Body size uint8_t ResSpc[4]; // Number of 0x00 to add to the end of Body uint8_t Length[4]; // Memory Size Segment uint8_t pad1[1]; uint8_t LabLen[1]; // Length Names: 10 uint8_t NumLen[1]; // Size = 4 numbers uint8s uint8_t Version[1]; // OMF Version: 2 uint8_t BankSize[4]; // Size of a Bank: 64 KB code if, between 0 and 64 KB for Data uint8_t Kind[2]; // Type Segment uint8_t pad2[2]; uint8_t Org[4]; uint8_t Align[4]; // Alignment: 0, 64 or 256 KB uint8_t NumSEx[1]; // Little Endian: 0 for IIgs uint8_t pad3[1]; uint8_t SegNum[2]; // Segment Number: 1-> N uint8_t EntryPointOffset[4]; // Entry point in the Segment uint8_t DispNameOffset[2]; // Where the offset is located LoadName uint8_t DispDataOffset[2]; // Offset begins the Body Segment uint8_t tempOrg[4]; // }; // write a number of bytes of a value static void _writeNBytes(uint8_t *dest, int bytes, int value) { while (bytes--) { *dest++ = (uint8_t)value; value >>= 8; } } // sort relocs before writing GS OS reloc instructions static int sortRelocByOffs(const void *A, const void *B) { return ((const Reloc*)A)->section_offset - ((const Reloc*)B)->section_offset; } // Export an Apple II GS relocatable executable StatusCode Asm::WriteA2GS_OMF(strref filename, bool full_collapse) { // determine the section with startup code - either first loaded object file or current file int first_section = 0; for (int s = 1; s<(int)allSections.size(); s++) { if (allSections[s].type==ST_CODE && (allSections[first_section].type != ST_CODE || (!allSections[s].include_from && allSections[first_section].include_from))) first_section = s; } // collapse all section together that share the same name StatusCode status = MergeSectionsByName(first_section); if (status!=STATUS_OK) { return status; } // Zero page section for x65 implies addresses, OMF direct-page/stack seg implies size of direct page + stack // so resolve the zero page sections first status = LinkZP(); if (status!=STATUS_OK) { return status; } // full collapse means that all sections gets merged into one // code+data section and one bss section which will be appended if (full_collapse) { status = MergeAllSections(first_section); if (status!=STATUS_OK) { return status; } } // determine if there is a direct page stack int DP_Stack_Size = 0; // 0 => default size (don't include) for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) { if (s->type==ST_ZEROPAGE) { s->type = ST_REMOVED; } if (s->type == ST_BSS && s->name.same_str("directpage_stack")) { DP_Stack_Size += s->addr_size(); s->type = ST_REMOVED; } } std::vector<int> SegNum; // order of valid segments std::vector<int> SegLookup; // inverse of SegNum SegNum.reserve(allSections.size()); SegLookup.reserve(allSections.size()); int reloc_max = 1; // OMF super instructions work by incremental addresses, sort relocs to simplify output for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) { if (first_section==SectionId(*s)) { SegNum.insert(SegNum.begin(), SectionId(*s)); } else if (s->type!=ST_REMOVED) { SegNum.push_back(SectionId(*s)); } SegLookup.push_back(-1); if ((s->type == ST_CODE || s->type == ST_DATA) && s->pRelocs && s->pRelocs->size() > 1) { qsort(&(*s->pRelocs)[0], s->pRelocs->size(), sizeof(Reloc), sortRelocByOffs); if ((int)s->pRelocs->size()>reloc_max) { reloc_max = (int)s->pRelocs->size(); } } } // reloc_max needs to greater than zero if (reloc_max<1) { return ERROR_ABORTED; } for (std::vector<int>::iterator i = SegNum.begin(); i!=SegNum.end(); ++i) { SegLookup[*i] = (int)(&*i-&SegNum[0]); } uint8_t *instructions = (uint8_t*)malloc(reloc_max * 16); if (!instructions) { return ERROR_OUT_OF_MEMORY; } // open a file for writing FILE *f = fopen(strown<512>(filename).c_str(), "wb"); if (!f) { free(instructions); return ERROR_CANT_WRITE_TO_FILE; } // consume all the relocs struct OMFSegHdr hdr = { 0 }; // initialize segment header hdr.NumLen[0] = 4; // numbers are 4 bytes under GS OS hdr.Version[0] = 2; // version is 2 for GS OS hdr.BankSize[2] = 1; // 64k banks _writeNBytes(hdr.DispNameOffset, 2, sizeof(hdr)); // start of file name (10 chars) strref fileBase = export_base_name; char segfile[10]; memset(segfile, ' ', 10); memcpy(segfile, fileBase.get(), fileBase.get_len() > 10 ? 10 : fileBase.get_len()); for (std::vector<int>::iterator i = SegNum.begin(); i != SegNum.end(); ++i) { Section &s = allSections[*i]; strref segName = s.name ? s.name : (s.type == ST_CODE ? strref("CODE") : strref("DATA")); // support zero bytes at end of block int num_zeroes_at_end = s.addr_size() - s.size(); int num_bytes_file = s.size(); while (num_bytes_file && s.output[num_bytes_file - 1] == 0) { num_zeroes_at_end++; num_bytes_file--; } _writeNBytes(hdr.SegNum, 2, SegLookup[*i] + 1); _writeNBytes(hdr.Kind, 2, s.type == ST_CODE ? 0x1000 : (s.type == ST_ZEROPAGE ? 0x12 : 0x1001)); _writeNBytes(hdr.DispDataOffset, 2, sizeof(hdr) + 10 + 1 + (int)segName.get_len()); _writeNBytes(hdr.Length, 4, num_bytes_file + num_zeroes_at_end); _writeNBytes(hdr.ResSpc, 4, num_zeroes_at_end); _writeNBytes(hdr.Align, 4, s.align_address > 1 ? 256 : 0); // instruction list starts with a LCONST + Length(4) + binary, the relocs begin after that int instruction_offs = 0; instructions[instruction_offs++] = OMFR_LCONST; _writeNBytes(instructions+instruction_offs, 4, num_bytes_file); instruction_offs += 4; if (s.pRelocs && s.pRelocs->size()) { // insert all SUPER_RELOC2 / SUPER_RELOC3 for (int b = 0; b <= 1; b++) { int count_offs = -1; int prev_page = -1; int inst_curr = instruction_offs; instructions[inst_curr++] = OMFR_SUPER; int len_offs = inst_curr; inst_curr += 4; instructions[inst_curr++] = (uint8_t)b; // SUPER_RELOC2 / SUPER_RELOC3 // try all SUPER_RELOC2 (2 bytes self reference, no shift) relocList::iterator r = s.pRelocs->begin(); while (r != s.pRelocs->end()) { if (r->shift == 0 && r->bytes == (b+2) && r->target_section == SectionId(s)) { if ((r->section_offset >> 8) != prev_page) { instructions[inst_curr++] = uint8_t(0x80 | ((r->section_offset >> 8) - prev_page - 1)); count_offs = -1; } // update patch counter for current page or add a new counter if (count_offs < 0) { count_offs = inst_curr; instructions[inst_curr++] = 0; } else instructions[count_offs]++; prev_page = r->section_offset>>8; instructions[inst_curr++] = (uint8_t)r->section_offset; // write patch offset into binary _writeNBytes(s.output + r->section_offset, b + 2, r->base_value); // patch binary with base value r = s.pRelocs->erase(r); } else ++r; } if (inst_curr > (instruction_offs + 6)) { _writeNBytes(instructions + len_offs, 4, inst_curr - instruction_offs - 5); instruction_offs = inst_curr; } } // insert all other records as they are encountered relocList::iterator r = s.pRelocs->begin(); while (r != s.pRelocs->end()) { if (r->target_section == SectionId(s)) { // this is a reloc, check if cRELOC is ok or if need RELOC bool cRELOC = r->section_offset < 0x10000 && r->base_value < 0x10000; instructions[instruction_offs++] = uint8_t(cRELOC ? OMFR_cRELOC : OMFR_RELOC); instructions[instruction_offs++] = r->bytes; instructions[instruction_offs++] = r->shift; _writeNBytes(instructions + instruction_offs, cRELOC ? 2 : 4, r->section_offset); instruction_offs += cRELOC ? 2 : 4; _writeNBytes(instructions + instruction_offs, cRELOC ? 2 : 4, r->base_value); instruction_offs += cRELOC ? 2 : 4; } else { // this is an interseg bool cINTERSEG = r->section_offset < 0x10000 && r->base_value < 0x10000; instructions[instruction_offs++] = uint8_t(cINTERSEG ? OMFR_cINTERSEG : OMFR_INTERSEG); instructions[instruction_offs++] = r->bytes; instructions[instruction_offs++] = r->shift; _writeNBytes(instructions + instruction_offs, cINTERSEG ? 2 : 4, r->section_offset); instruction_offs += cINTERSEG ? 2 : 4; _writeNBytes(instructions + instruction_offs, cINTERSEG ? 0: 2 , 1); // file number = 1 instruction_offs += cINTERSEG ? 0 : 2; _writeNBytes(instructions + instruction_offs, cINTERSEG ? 1 : 2, SegLookup[r->target_section] + 1); // segment number starting from 1 instruction_offs += cINTERSEG ? 1 : 2; _writeNBytes(instructions + instruction_offs, cINTERSEG ? 2 : 4, r->base_value); instruction_offs += cINTERSEG ? 2 : 4; } r = s.pRelocs->erase(r); } } instructions[instruction_offs++] = OMFR_END; // size of seg = file header + 10 bytes file name + 1 byte seg name length + seg nameh + seg.addr_size() + instruction_size int segSize = sizeof(hdr) + 10 + 1 + (int)segName.get_len() + num_bytes_file + instruction_offs; if (num_bytes_file==0) { segSize -= 5; } _writeNBytes(hdr.SegTotal, 4, segSize); uint8_t lenSegName = (uint8_t)segName.get_len(); fwrite(&hdr, sizeof(hdr), 1, f); fwrite(segfile, 10, 1, f); fwrite(&lenSegName, 1, 1, f); fwrite(segName.get(), segName.get_len(), 1, f); if (num_bytes_file) { fwrite(instructions, 5, 1, f); // $f2 + 4 bytes data size fwrite(s.output, num_bytes_file, 1, f); // segment data } if (instruction_offs > 5) fwrite(instructions + 5, instruction_offs - 5, 1, f); // reloc instructions } // if there is a size of the direct page & stack, write it if (DP_Stack_Size) { strref segName("DPStack"); char lenSegName = (char)segName.get_len(); _writeNBytes(hdr.SegNum, 2, (int)SegNum.size()+1); _writeNBytes(hdr.Kind, 2, 0x12); _writeNBytes(hdr.DispDataOffset, 2, sizeof(hdr) + 10 + 1 + (int)segName.get_len()); _writeNBytes(hdr.Length, 4, DP_Stack_Size); _writeNBytes(hdr.ResSpc, 4, DP_Stack_Size); _writeNBytes(hdr.Align, 4, 256); int segSize = sizeof(hdr) + 10 + 1 + (int)segName.get_len() + 1; _writeNBytes(hdr.SegTotal, 4, segSize); instructions[0] = 0; fwrite(&hdr, sizeof(hdr), 1, f); fwrite(segfile, 10, 1, f); fwrite(&lenSegName, 1, 1, f); fwrite(segName.get(), segName.get_len(), 1, f); fwrite(instructions, 1, 1, f); // end instruction } free(instructions); fclose(f); return STATUS_OK; } int main(int argc, char **argv) { int return_value = 0; bool load_header = true; bool size_header = false; bool info = false; bool gen_allinstr = false; bool gs_os_reloc = false; bool force_merge_sections = false; bool list_output = false; bool tass_list_output = false; Asm assembler; const char *source_filename = nullptr, *obj_out_file = nullptr; const char *binary_out_name = nullptr; const char *sym_file = nullptr, *vs_file = nullptr, *cmdarg_tass_labels_file = nullptr; strref list_file, allinstr_file; strref tass_list_file; for (int a = 1; a<argc; a++) { if (argv[a][0]=='-') { strref arg(argv[a]+1); if (arg.get_first()=='i') { assembler.AddIncludeFolder(arg+1); } else if (arg.same_str(cmdarg_kickasm) ) { assembler.syntax = SYNTAX_KICKASM; } else if (arg.same_str(cmdarg_merlin)) { assembler.syntax = SYNTAX_MERLIN; } else if (arg.get_first()=='D'||arg.get_first()=='d') { ++arg; if (arg.find('=')>0) { assembler.AssignLabel(arg.before('='), arg.after('=')); } else { assembler.AssignLabel(arg, "1"); } } else if (arg.same_str(cmdarg_c64)) { load_header = true; size_header = false; } else if (arg.same_str(cmdarg_a2b)) { assembler.default_org = 0x0803; load_header = true; size_header = true; } else if (arg.same_str(cmdarg_bin)) { load_header = false; size_header = false; } else if (arg.same_str(cmdarg_a2p)) { assembler.default_org = 0x2000; load_header = false; size_header = false; } else if (arg.same_str(cmdarg_a2o)) { gs_os_reloc = true; } else if (arg.same_str(cmdarg_mrg)) { force_merge_sections = true; } else if (arg.same_str(cmdarg_sect)) { info = true; } else if (arg.same_str(cmdarg_endmacro)) { assembler.end_macro_directive = true; } else if (arg.same_str(cmdarg_xrefimp)) { assembler.import_means_xref = true; } else if (arg.has_prefix(cmdarg_listing)&&(arg.get_len()==cmdarg_listing.get_len()||arg[cmdarg_listing.get_len()]=='=')) { assembler.list_assembly = true; list_output = true; list_file = arg.after( '=' ); } else if (arg.has_prefix(cmdarg_tass_listing)&&(arg.get_len()==cmdarg_listing.get_len()||arg[cmdarg_listing.get_len()]=='=')) { assembler.list_assembly = true; tass_list_output = true; tass_list_file = arg.after( '=' ); } else if (arg.has_prefix(cmdarg_allinstr)&&(arg.get_len()==cmdarg_allinstr.get_len()||arg[cmdarg_allinstr.get_len()]=='=')) { gen_allinstr = true; allinstr_file = arg.after('='); } else if (arg.has_prefix(cmdarg_org)) { arg = arg.after('='); if (arg && arg.get_first()=='$' && arg.get_len()>1) { assembler.default_org = (int)(arg+1).ahextoui(); } else if (arg.is_number()) { assembler.default_org = (int)arg.atoi(); } // force the current section to be org'd assembler.AssignAddressToSection(assembler.SectionId(), assembler.default_org); } else if (arg.has_prefix(cmdarg_acc)&&arg[cmdarg_acc.get_len()]=='=') { assembler.accumulator_16bit = arg.after('=').atoi()==16; } else if (arg.has_prefix(cmdarg_xy)&&arg[cmdarg_xy.get_len()]=='=') { assembler.index_reg_16bit = arg.after('=').atoi()==16; } else if (arg.has_prefix(cmdarg_cpu)&&(arg.get_len()==cmdarg_cpu.get_len()||arg[cmdarg_cpu.get_len()]=='=')) { arg.split_token_trim('='); bool found = false; for (int c = 0; c<nCPUs; c++) { if (arg) { if (arg.same_str(aCPUs[c].name)) { assembler.SetCPU((CPUIndex)c); found = true; break; } } else { printf("%s\n", aCPUs[c].name); } } if (!found && arg) { printf("ERROR: UNKNOWN CPU " STRREF_FMT "\n", STRREF_ARG(arg)); return 1; } if (!arg) { return 0; } } else if (arg.same_str(cmdarg_sym)&&(a+1)<argc) { sym_file = argv[++a]; } else if (arg.same_str(cmdarg_obj)&&(a+1)<argc) { obj_out_file = argv[++a]; } else if (arg.same_str(cmdarg_vice)&&(a+1)<argc) { vs_file = argv[++a]; } else if (arg.same_str(cmdarg_tass_labels)&&(a+1)<argc) { cmdarg_tass_labels_file = argv[++a]; } else { printf("Unexpected option " STRREF_FMT "\n", STRREF_ARG(arg)); } } else if (!source_filename) { source_filename = argv[a]; } else if (!binary_out_name) { binary_out_name = argv[a]; } } for (int a = 1; a < argc; a++) { strref arg(argv[a]); printf(STRREF_FMT "\n", STRREF_ARG(arg)); } if (gen_allinstr) { assembler.AllOpcodes(allinstr_file); } else if (!source_filename) { puts("Usage:\n" " x65 filename.s code.prg [options]\n" " * -i(path) : Add include path\n" " * -D(label)[=value] : Define a label with an optional value (otherwise defined as 1)\n" " * -cpu=6502/65c02/65c02wdc/65816: assemble with opcodes for a different cpu\n" " * -acc=8/16: set the accumulator mode for 65816 at start, default is 8 bits\n" " * -xy=8/16: set the index register mode for 65816 at start, default is 8 bits\n" " * -org = $2000 or - org = 4096: force fixed address code at address\n" " * -obj (file.x65) : generate object file for later linking\n" " * -bin : Raw binary\n" " * -c64 : Include load address(default)\n" " * -a2b : Apple II Dos 3.3 Binary\n" " * -a2p : Apple II ProDos Binary\n" " * -a2o : Apple II GS OS executable (relocatable)\n" " * -mrg : Force merge all sections (use with -a2o)\n" " * -sym (file.sym) : symbol file\n" " * -lst / -lst = (file.lst) : generate disassembly text from result(file or stdout)\n" " * -opcodes / -opcodes = (file.s) : dump all available opcodes(file or stdout)\n" " * -sect: display sections loaded and built\n" " * -vice (file.vs) : export a vice symbol file\n" " * -merlin: use Merlin syntax\n" " * -endm : macros end with endm or endmacro instead of scoped('{' - '}')\n"); return 0; } // Load source if (source_filename) { size_t size = 0; strref srcname(source_filename); assembler.export_base_name = strref(binary_out_name).after_last_or_full('/', '\\').before_or_full('.'); if (char *buffer = assembler.LoadText(srcname, size)) { // if source_filename contains a path add that as a search path for include files assembler.AddIncludeFolder(srcname.before_last('/', '\\')); assembler.Assemble(strref(buffer, strl_t(size)), srcname, obj_out_file != nullptr); if (assembler.error_encountered) { return_value = 1; } else { // export object file (this can be done at the same time as building a binary) if (obj_out_file) { assembler.WriteObjectFile(obj_out_file); } // if exporting binary or relocatable executable, complete the build if (binary_out_name && !srcname.same_str(binary_out_name)) { if (gs_os_reloc) assembler.WriteA2GS_OMF(binary_out_name, force_merge_sections); else { strref binout(binary_out_name); strref ext = binout.after_last('.'); if (ext) { binout.clip(ext.get_len()+1); } strref aAppendNames[MAX_EXPORT_FILES]; StatusCode err = assembler.LinkZP(); // link zero page sections if (err > FIRST_ERROR) { assembler.PrintError(strref(), err); return_value = 1; } int numExportFiles = assembler.GetExportNames(aAppendNames, MAX_EXPORT_FILES); for (int e = 0; e < numExportFiles; e++) { strown<512> file(binout); file.append(aAppendNames[e]); file.append('.'); file.append(ext); int size_export; int addr; if (uint8_t *buf = assembler.BuildExport(aAppendNames[e], size_export, addr)) { if (FILE *f = fopen(file.c_str(), "wb")) { if (load_header) { uint8_t load_addr[2] = { (uint8_t)addr, (uint8_t)(addr >> 8) }; fwrite(load_addr, 2, 1, f); } if (size_header) { uint8_t byte_size[2] = { (uint8_t)size_export, (uint8_t)(size_export >> 8) }; fwrite(byte_size, 2, 1, f); } fwrite(buf, size_export, 1, f); fclose(f); } free(buf); } } } } // print encountered sections info if (info) { printf("SECTIONS SUMMARY\n================\n"); for (size_t i = 0; i < assembler.allSections.size(); ++i) { Section &s = assembler.allSections[i]; if (s.address > s.start_address) { printf("Section %d: \"" STRREF_FMT "\" Dummy: %s Relative: %s Merged: %s Start: 0x%04x End: 0x%04x\n", (int)i, STRREF_ARG(s.name), s.dummySection ? "yes" : "no", s.IsRelativeSection() ? "yes" : "no", s.IsMergedSection() ? "yes" : "no", s.start_address, s.address); if (s.pRelocs) { for (relocList::iterator rel = s.pRelocs->begin(); rel != s.pRelocs->end(); ++rel) printf("\tReloc value $%x at offs $%x section %d\n", rel->base_value, rel->section_offset, rel->target_section); } } } } // listing after export since addresses are now resolved if ( list_output ) assembler.List(list_file); if( tass_list_output ) assembler.ListTassStyle(tass_list_file); // export .sym file if (sym_file && !srcname.same_str(sym_file) && !assembler.map.empty()) { if (FILE *f = fopen(sym_file, "w")) { bool wasLocal = false; for (MapSymbolArray::iterator i = assembler.map.begin(); i!=assembler.map.end(); ++i) { uint32_t value = (uint32_t)i->value; if (size_t(i->section) < assembler.allSections.size()) { value += assembler.allSections[i->section].start_address; } fprintf(f, "%s.label " STRREF_FMT " = $%04x", wasLocal==i->local ? "\n" : (i->local ? " {\n" : "\n}\n"), STRREF_ARG(i->name), value); wasLocal = i->local; } fputs(wasLocal ? "\n}\n" : "\n", f); fclose(f); } } // export vice monitor commands if (vs_file && !srcname.same_str(vs_file) && !assembler.map.empty()) { if (FILE *f = fopen(vs_file, "w")) { for (MapSymbolArray::iterator i = assembler.map.begin(); i!=assembler.map.end(); ++i) { uint32_t value = (uint32_t)i->value; if (size_t(i->section) < assembler.allSections.size()) { value += assembler.allSections[i->section].start_address; } if(i->name.same_str("debugbreak")) { fprintf(f, "break $%04x\n", value); } else { fprintf(f, "al $%04x %s" STRREF_FMT "\n", value, i->name[0]=='.' ? "" : ".", STRREF_ARG(i->name)); } } fclose(f); } } // export tass labels if( cmdarg_tass_labels_file && !srcname.same_str( cmdarg_tass_labels_file ) && !assembler.map.empty() ) { if( FILE *f = fopen( cmdarg_tass_labels_file, "w" ) ) { for( MapSymbolArray::iterator i = assembler.map.begin(); i != assembler.map.end(); ++i ) { uint32_t value = ( uint32_t )i->value; if( size_t( i->section ) < assembler.allSections.size() ) { value += assembler.allSections[ i->section ].start_address; } if( i->name.same_str( "debugbreak" ) ) {} else { strown<256> line; line.append( i->name ); line.sprintf_append( "\t= $%04x\n", value); fprintf(f, line.c_str()); } } fclose( f ); } } } // free some memory assembler.Cleanup(); } } return return_value; }