diff --git a/docs/65816.txt b/docs/65816.txt index 1e86ce2..51e9629 100644 --- a/docs/65816.txt +++ b/docs/65816.txt @@ -82,7 +82,21 @@ method obviously is not a good example on structured programming. :) Section: Miscellaneous ---------------------------------------------------------------------- -Note that ACME cannot produce more than 64 KBytes of code. Also note -that though the 65816 CPU has an address space of 16 MB, ACME's -program counter is only sixteen bits wide. It shouldn't be too hard to -make any assembled code run in a non-zero bank, though. +The 65816 CPU has an address space of 16 MBytes, but that is still +split up into 256 "banks" of 64 KBytes each: A simple "JMP $1234" +instruction does not necessarily jump to $001234, it rather jumps to +address $1234 _in_the_bank_where_the_code_is_currently_running_. +Therefore, it does not make sense to use 24-bit values for most +labels. On the other hand, when jumping between banks, you need the +full 24-bit address. +One solution for this problem would be something like this: + + ; this code is supposed to run at $1000 in bank 2: + * = $021000 ; we use the correct 24-bit address, but we + !pseudopc * & $ffff { ; restrict the pc to 16 bits +start lda some_var ; now "start" is $1000 + beq some_label + ... + } +This way, referencing "start" will give you the 16-bit value ($1000), +but referencing "&start" will give the full 24-bit value ($021000). diff --git a/docs/Errors.txt b/docs/Errors.txt index c7e0c13..f638375 100644 --- a/docs/Errors.txt +++ b/docs/Errors.txt @@ -491,8 +491,9 @@ Out of memory. will ever see this error, though. ;) Reached memory limit. - The program counter reached address $10000, leaving the output - buffer. At the moment, ACME can only produce a maximum of 64 KB. + This error is reported if the write index for the output buffer + exceeds 16 megabytes. This is an arbitrary limit - if you have a + good reason for having it raised further, tell me. :D Syntax error. This is only given as a _serious_ error if it's in a "!do" loop diff --git a/docs/Help.txt b/docs/Help.txt index 3bb33a0..022b917 100644 --- a/docs/Help.txt +++ b/docs/Help.txt @@ -83,7 +83,6 @@ Section: What it can and does ACME is a crossassembler. ACME can produce code for the 6502, 65c02 and 65816 processors. It does this *fast*. -It can produce at most 64 KBytes of code. You can use global labels, local labels and anonymous labels. It is fast. You can use global and local macros. @@ -120,7 +119,6 @@ Section: What it can't and doesn't ACME cannot transfer data to a C64 or another computer. ACME does not produce ".o65"-format linkable object files. -ACME cannot produce more than 64 KB (would be useful for the 65816). ACME cannot disassemble or relocate given code files. diff --git a/src/acme.c b/src/acme.c index 70590fd..5637322 100644 --- a/src/acme.c +++ b/src/acme.c @@ -687,7 +687,6 @@ static const char *long_option(const char *string) else if (strcmp(string, OPTION_TEST) == 0) { config.dialect = V__FUTURE_VERSION; config.test_new_features = TRUE; - config.outbuf_size = 0x1000000; // 16 MiB (FIXME - give it its own cli switch!) } PLATFORM_LONGOPTION_CODE else if (strcmp(string, OPTION_COLOR) == 0) config.format_color = TRUE; @@ -797,17 +796,17 @@ int main(int argc, const char *argv[]) // now that we have processed all cli switches, check a few values for // valid range: - if ((config.initial_pc != NO_VALUE_GIVEN) && (config.initial_pc >= config.outbuf_size)) { - fprintf(stderr, "%sProgram counter exceeds outbuffer size.\n", cliargs_error); + if ((config.initial_pc != NO_VALUE_GIVEN) && (config.initial_pc >= OUTBUF_MAXSIZE)) { + fprintf(stderr, "%sProgram counter exceeds maximum outbuffer size.\n", cliargs_error); exit(EXIT_FAILURE); } - if ((config.outfile_start != NO_VALUE_GIVEN) && (config.outfile_start >= config.outbuf_size)) { - fprintf(stderr, "%sStart address of output file exceeds outbuffer size.\n", cliargs_error); + if ((config.outfile_start != NO_VALUE_GIVEN) && (config.outfile_start >= OUTBUF_MAXSIZE)) { + fprintf(stderr, "%sStart address of output file exceeds maximum outbuffer size.\n", cliargs_error); exit(EXIT_FAILURE); } // "limit" is end+1 and therefore we need ">" instead of ">=": - if ((config.outfile_limit != NO_VALUE_GIVEN) && (config.outfile_limit > config.outbuf_size)) { - fprintf(stderr, "%sEnd+1 of output file exceeds outbuffer size.\n", cliargs_error); + if ((config.outfile_limit != NO_VALUE_GIVEN) && (config.outfile_limit > OUTBUF_MAXSIZE)) { + fprintf(stderr, "%sEnd+1 of output file exceeds maximum outbuffer size.\n", cliargs_error); exit(EXIT_FAILURE); } if ((config.outfile_start != NO_VALUE_GIVEN) diff --git a/src/config.h b/src/config.h index a3edeea..85628c8 100644 --- a/src/config.h +++ b/src/config.h @@ -20,6 +20,7 @@ typedef unsigned int bits; typedef unsigned int scope_t; typedef signed int intval_t; // at least 32 bits typedef unsigned int uintval_t; // at least 32 bits (only used for logical shift right) +#define OUTBUF_MAXSIZE 0x1000000 // 16 MiB ought to be enough for anybody // struct to remember where macros were defined (FIXME - use for symbols as well!) struct location { diff --git a/src/cpu.c b/src/cpu.c index 7680dfc..f4def23 100644 --- a/src/cpu.c +++ b/src/cpu.c @@ -15,62 +15,72 @@ static struct cpu_type cpu_type_6502 = { keyword_is_6502_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR | CPUFLAG_INDIRECTJMPBUGGY, // warn about "XYZ ($ff),y" and "jmp ($XYff)" - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_nmos6502 = { keyword_is_nmos6502_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR | CPUFLAG_INDIRECTJMPBUGGY | CPUFLAG_8B_AND_AB_NEED_0_ARG, // ANE/LXA #$xx are unstable unless arg is $00 - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_c64dtv2 = { keyword_is_c64dtv2_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR | CPUFLAG_INDIRECTJMPBUGGY | CPUFLAG_8B_AND_AB_NEED_0_ARG, - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_65c02 = { keyword_is_65c02_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR, // from WDC docs - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_r65c02 = { keyword_is_r65c02_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR, // from WDC docs - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_w65c02 = { keyword_is_w65c02_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR, // from WDC docs - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_65816 = { keyword_is_65816_mnemo, // TODO - what about CPUFLAG_WARN_ABOUT_FF_PTR? only needed for old opcodes in emulation mode! CPUFLAG_SUPPORTSLONGREGS, // allows A and XY to be 16bits wide - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_65ce02 = { keyword_is_65ce02_mnemo, CPUFLAG_DECIMALSUBTRACTBUGGY, // SBC does not work reliably in decimal mode - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_4502 = { keyword_is_4502_mnemo, CPUFLAG_DECIMALSUBTRACTBUGGY, // SBC does not work reliably in decimal mode - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; static struct cpu_type cpu_type_m65 = { keyword_is_m65_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR, // TODO - remove this? check datasheets/realhw! - 0xffff, +// 0xffff, + 0x200, 234 // !align fills with "NOP" }; diff --git a/src/cpu.h b/src/cpu.h index 652d44b..64f6931 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -17,7 +17,8 @@ struct cpu_type { // because that's where the mnemonic is stored! boolean (*keyword_is_mnemonic)(int); bits flags; // see below for bit meanings - int pc_mask; // last value before program counter wraps to zero +// int pc_mask; // last value before program counter wraps to zero + int dummy_pc; // value to use for pc after complaining to user they did not set it unsigned char default_align_value; //int reserved_keywords_maxlen; // TODO - add //int (*reserved_keyword_check)(void); // TODO - add diff --git a/src/global.c b/src/global.c index eddb3b5..e789821 100644 --- a/src/global.c +++ b/src/global.c @@ -123,7 +123,6 @@ void config_default(struct config *conf) conf->test_new_features = FALSE; // enabled by --test conf->dialect = V__CURRENT_VERSION; // changed by --dialect conf->debuglevel = DEBUGLEVEL_DEBUG; // changed by --debuglevel, used by "!debug" - conf->outbuf_size = 0x10000; // 64K, "--test" changes to 16M conf->initial_cpu_type = NULL; conf->symbollist_filename = NULL; conf->vicelabels_filename = NULL; diff --git a/src/global.h b/src/global.h index ead7690..dd30d85 100644 --- a/src/global.h +++ b/src/global.h @@ -13,10 +13,11 @@ #include "config.h" +// constants + #define LOCAL_PREFIX '.' // DO NOT CHANGE (or change expression parser accordingly) #define CHEAP_PREFIX '@' // prefix character for cheap locals - -// Constants +#define NO_VALUE_GIVEN (-1) // used when only non-negative values are valid, e.g. for addresses extern char s_untitled[]; // error messages during assembly @@ -89,14 +90,13 @@ struct config { boolean test_new_features; // FALSE, enabled by --test enum dialect dialect; // set by --dialect (and --test --test) int debuglevel; // set by --debuglevel, used by "!debug" - intval_t outbuf_size; // 64K, "--test" changes to 16M const struct cpu_type *initial_cpu_type; const char *symbollist_filename; const char *vicelabels_filename; const char *output_filename; // TODO - put in "part" struct enum outfile_format outfile_format; const char *report_filename; // TODO - put in "part" struct -#define NO_VALUE_GIVEN (-1) // default value for these fields if cli switch not used: +// default value for these fields (if cli switch not used) is NO_VALUE_GIVEN: int mem_init_value; // set by --initmem intval_t initial_pc; // set by --setpc intval_t outfile_start; // set by --from-to diff --git a/src/output.c b/src/output.c index 32f12d7..8cf612c 100644 --- a/src/output.c +++ b/src/output.c @@ -30,10 +30,12 @@ struct segment { // structure for all output stuff: struct output { // output buffer stuff - char *buffer; // holds assembled code (size is config.outbuf_size) + char *buffer; // holds assembled code (size is needed_bufsize) intval_t write_idx; // index of next write intval_t lowest_written; // smallest address used intval_t highest_written; // largest address used + intval_t forced_start_idx; // first index to go into output file + intval_t forced_limit_idx; // first index to not go into output file struct { intval_t start; // start of current segment (or NO_SEGMENT_START) intval_t max; // highest address segment may use @@ -41,6 +43,7 @@ struct output { struct segment list_head; // head element of doubly-linked ring list } segm; char xor; // output modifier + intval_t needed_bufsize; // calculated at end of each pass }; // for offset assembly: @@ -85,7 +88,7 @@ static void find_segment_max(intval_t new_pc) while (test_segment->start <= new_pc) test_segment = test_segment->next; if (test_segment == &out->segm.list_head) - out->segm.max = config.outbuf_size - 1; + out->segm.max = OUTBUF_MAXSIZE - 1; else out->segm.max = test_segment->start - 1; // last free address available } @@ -97,7 +100,7 @@ static void border_crossed(int current_offset) // FIXME - find a way to make this a normal error instead of a serious one, // so it can be suppressed until we are sure the program won't shrink any // further: - if (current_offset >= config.outbuf_size) + if (current_offset >= OUTBUF_MAXSIZE) Throw_serious_error("Reached memory limit."); if (pass.flags.throw_segment_messages) { throw_message(config.debuglevel_segmentprobs, "Segment reached another one, overwriting it.", NULL); @@ -135,13 +138,18 @@ static void real_output(intval_t byte) ++statement_size; // count this byte } +// throw "program counter not defined" error and then use a fake pc so this does +// not happen again +static void complain_and_use_dummy_pc(void) +{ + Throw_error(exception_pc_undefined); + vcpu_set_pc(cpu_current_type->dummy_pc, 0); // 0 = no flags +} // throw error (pc undefined) and use fake pc from now on static void no_output(intval_t byte) { - Throw_error(exception_pc_undefined); - // now change fn ptr to not complain again. - output_byte = real_output; + complain_and_use_dummy_pc(); output_byte(byte); // try again } @@ -160,10 +168,9 @@ void output_skip(int size) } // check whether ptr undefined - if (output_byte == no_output) { - output_byte(0); // trigger error with a dummy byte - --size; // fix amount to cater for dummy byte - } + if (output_byte == no_output) + complain_and_use_dummy_pc(); + // CAUTION - there are two copies of these checks! // TODO - add additional check for current segment's "limit" value // did we reach next segment? @@ -182,29 +189,19 @@ void output_skip(int size) // remember current outbuf index as start/limit of output file -static boolean force_file_start = FALSE; -static intval_t forced_start_idx; void outbuf_set_outfile_start(void) { // check whether ptr undefined - if (output_byte == no_output) { - Throw_error(exception_pc_undefined); - } else { - force_file_start = TRUE; - forced_start_idx = out->write_idx; - } + if (output_byte == no_output) + complain_and_use_dummy_pc(); + out->forced_start_idx = out->write_idx; } -static boolean force_file_limit = FALSE; -static intval_t forced_limit_idx; void outbuf_set_outfile_limit(void) { // check whether ptr undefined - if (output_byte == no_output) { - Throw_error(exception_pc_undefined); - } else { - force_file_limit = TRUE; - forced_limit_idx = out->write_idx; - } + if (output_byte == no_output) + complain_and_use_dummy_pc(); + out->forced_limit_idx = out->write_idx; } @@ -260,31 +257,56 @@ static void check_segment(intval_t new_pc) // init structs void output_init(void) { - out->buffer = NULL; // init ring list of segments (FIXME - move to passinit) out->segm.list_head.next = &out->segm.list_head; out->segm.list_head.prev = &out->segm.list_head; + // init the one field not initialized by output_passinit: + out->needed_bufsize = NO_VALUE_GIVEN; } // clear segment list and disable output void output_passinit(void) { -// struct segment *temp; + //struct segment *temp; - // are we supposed to actually generate correct output? + // init output struct: if (pass.flags.generate_output) { - // allocate output buffer - // FIXME - use size info gathered in previous pass! - out->buffer = safe_malloc(config.outbuf_size); + // we are supposed to actually generate correct output, so + // allocate and init output buffer: + if (out->needed_bufsize == NO_VALUE_GIVEN) { + // this is not an error. it happens when the source code + // does not create a single output byte, for example if + // someone uses ACME simply for "!info 3456 / 78" + out->needed_bufsize = 16; // actually 1 would suffice... + } + if (out->needed_bufsize > OUTBUF_MAXSIZE) { + Throw_serious_error("Output buffer size exceeds maximum."); + } + //fprintf(stderr, "Allocating outbuf of size 0x%06x.\n", out->needed_bufsize); + out->buffer = safe_malloc(out->needed_bufsize); // fill output buffer with initial byte value if (config.mem_init_value == NO_VALUE_GIVEN) { - memset(out->buffer, 0, config.outbuf_size); // default value + memset(out->buffer, 0, out->needed_bufsize); // default is zero } else { - memset(out->buffer, config.mem_init_value & 0xff, config.outbuf_size); + memset(out->buffer, config.mem_init_value & 0xff, out->needed_bufsize); } + } else { + out->buffer = NULL; } - + // FIXME - this should be NO_VALUE_GIVEN, but then pseudopc offset would be off, and + // we cannot use NO_VALUE_GIVEN for pc as long as setting pc uses diff to old value! + out->write_idx = 0; // ...so we set it to the same value as pc on pass init! + // invalidate start and end (first byte actually written will fix them) + out->lowest_written = OUTBUF_MAXSIZE; // FIXME - add code so OUTBUF_MAXSIZE-1 is a hard limit! + out->highest_written = -1; + // no overrides for start and end yet: + out->forced_start_idx = NO_VALUE_GIVEN; + out->forced_limit_idx = NO_VALUE_GIVEN; + // not in a segment + out->segm.start = NO_SEGMENT_START; // TODO - "no active segment" could be made a segment flag! + out->segm.max = OUTBUF_MAXSIZE - 1; + out->segm.flags = 0; //FIXME - why clear ring list in every pass? // Because later pass shouldn't complain about overwriting the same segment from earlier pass! // Currently this does not happen because segment warnings are only generated in first pass. @@ -296,17 +318,13 @@ void output_passinit(void) // segment_list = segment_list->next; // free(temp); // } + // no "encryption": + out->xor = 0; + // needed size of buffer will be calculated at end of pass, so + //out->needed_bufsize = do not init - // invalidate start and end (first byte actually written will fix them) - out->lowest_written = config.outbuf_size - 1; - out->highest_written = 0; // deactivate output - any byte written will trigger error: output_byte = no_output; - out->write_idx = 0; // same as pc on pass init! - out->segm.start = NO_SEGMENT_START; // TODO - "no active segment" could be made a segment flag! - out->segm.max = config.outbuf_size - 1; // TODO - use end of bank? - out->segm.flags = 0; - out->xor = 0; //vcpu stuff: pc_ntype = NUMTYPE_UNDEFINED; // not defined yet @@ -372,6 +390,13 @@ static void end_segment(void) void output_endofpass(void) { end_segment(); + // calculate size of output buffer + if (out->highest_written >= out->lowest_written) { + out->needed_bufsize = out->highest_written + 1; + } else { + out->needed_bufsize = NO_VALUE_GIVEN; + } + //fprintf(stderr, "Need outbuf size of 0x%04x bytes.\n", out->needed_bufsize); } @@ -382,10 +407,15 @@ static void start_segment(intval_t address_change, bits segment_flags) end_segment(); // calculate start of new segment - out->write_idx = (out->write_idx + address_change) & (config.outbuf_size - 1); + out->write_idx = (out->write_idx + address_change); + if (out->write_idx < 0) { + Throw_serious_error("Tried to write to negative addresses."); + } else if (out->write_idx >= OUTBUF_MAXSIZE) { + Throw_serious_error("Reached memory limit."); + } out->segm.start = out->write_idx; out->segm.flags = segment_flags; - // allow writing to output buffer + // allow "writing to buffer" (even though buffer does not really exist until final pass) output_byte = real_output; // in first/last pass, check for other segments and maybe issue warning if (pass.flags.throw_segment_messages) { @@ -407,7 +437,7 @@ void output_set_xor(char xor) } -// set program counter to defined value (FIXME - allow for undefined!) +// set program counter to defined value // if start address was given on command line, main loop will call this before each pass. // in addition to that, it will be called on each "*= VALUE". void vcpu_set_pc(intval_t new_pc, bits segment_flags) @@ -416,39 +446,10 @@ void vcpu_set_pc(intval_t new_pc, bits segment_flags) pc_change = new_pc - program_counter; program_counter = new_pc; // FIXME - oversized values are accepted without error and will be wrapped at end of statement! - pc_ntype = NUMTYPE_INT; // FIXME - remove when allowing undefined! + pc_ntype = NUMTYPE_INT; // now tell output buffer to start a new segment start_segment(pc_change, segment_flags); } -/* -TODO - overhaul program counter and memory pointer stuff: -general stuff: PC and mem ptr might be marked as "undefined" via flags field. -However, their "value" fields are still updated, so we can calculate differences. - -on pass init: - if value given on command line, set PC and out ptr to that value - otherwise, set both to zero and mark as "undefined" -when ALU asks for "*": - return current PC (value and flags) -when encountering "!pseudopc VALUE { BLOCK }": - parse new value (NEW: might be undefined!) - remember difference between current and new value - set PC to new value - after BLOCK, use remembered difference to change PC back -when encountering "*= VALUE": - parse new value (NEW: might be undefined!) - calculate difference between current PC and new value - set PC to new value - tell outbuf to add difference to mem ptr (starting a new segment) - if new value is undefined, tell outbuf to disable output - -Problem: always check for "undefined"; there are some problematic combinations. -I need a way to return the size of a generated code block even if PC undefined. -Maybe like this: - *= new_address [, invisible] [, overlay] [, size_counter = symbol {] - ...code... - [} ; at end of block, size is written to symbol given above!] -*/ // get program counter @@ -471,19 +472,7 @@ int output_get_statement_size(void) // adjust program counter (called at end of each statement) void output_end_statement(void) { - if (config.dialect >= V0_98__PATHS_AND_SYMBOLCHANGE) { - program_counter += statement_size; - } else { - // older versions did this stupid crap: - program_counter = (program_counter + statement_size) & cpu_current_type->pc_mask; - // making the program counter automatically wrap around from - // 0xffff to 0x0000 without any warning or error is just asking - // for trouble. - // but I think I added this to fulfill a feature request where - // demo code was located at $ffxx and in zero page, and with the - // dialect feature I can actually be backward-compatible... - // ...so there. - } + program_counter += statement_size; statement_size = 0; // reset } @@ -502,10 +491,10 @@ void output_get_result(const char **ptr, intval_t *size, intval_t *loadaddr) start = out->lowest_written; limit = out->highest_written + 1; // if pseudo opcodes were used, they override the actual values: - if (force_file_start) - start = forced_start_idx; - if (force_file_limit) - limit = forced_limit_idx; + if (out->forced_start_idx != NO_VALUE_GIVEN) + start = out->forced_start_idx; + if (out->forced_limit_idx != NO_VALUE_GIVEN) + limit = out->forced_limit_idx; // if cli args were given, they override even harder: if (config.outfile_start != NO_VALUE_GIVEN) start = config.outfile_start; @@ -535,6 +524,10 @@ void pseudopc_start(struct number *new_pc) { struct pseudopc *new_context; + // check whether ptr undefined + if (output_byte == no_output) + complain_and_use_dummy_pc(); + new_context = safe_malloc(sizeof(*new_context)); // create new struct (this must never be freed, as it gets linked to labels!) new_context->outer = pseudopc_current_context; // let it point to previous one pseudopc_current_context = new_context; // make it the current one @@ -542,14 +535,14 @@ void pseudopc_start(struct number *new_pc) new_context->ntype = pc_ntype; new_context->offset = new_pc->val.intval - program_counter; program_counter = new_pc->val.intval; - pc_ntype = NUMTYPE_INT; // FIXME - remove when allowing undefined! + pc_ntype = NUMTYPE_INT; //new: pc_flags = new_pc->flags & (NUMBER_IS_DEFINED | NUMBER_EVER_UNDEFINED); } // end offset assembly void pseudopc_end(void) { - // FIXME - check this "wraparound" stuff, it may no longer make sense! - program_counter = (program_counter - pseudopc_current_context->offset) & (config.outbuf_size - 1); // pc might have wrapped around + program_counter = program_counter - pseudopc_current_context->offset; +// FIXME - if pc can wrap around, then we should have fixed the offset! pc_ntype = pseudopc_current_context->ntype; pseudopc_current_context = pseudopc_current_context->outer; // go back to outer block if (pseudopc_current_context == NULL) @@ -572,7 +565,7 @@ int pseudopc_unpseudo(struct number *target, struct pseudopc *context, unsigned return 1; // error } // FIXME - in future, check both target and context for NUMTYPE_UNDEFINED! - target->val.intval = (target->val.intval - context->offset) & (config.outbuf_size - 1); // FIXME - is masking really needed? TODO + target->val.intval = target->val.intval - context->offset; context = context->outer; } return 0; // ok diff --git a/src/pseudoopcodes.c b/src/pseudoopcodes.c index 24179cd..59ad91e 100644 --- a/src/pseudoopcodes.c +++ b/src/pseudoopcodes.c @@ -684,13 +684,12 @@ static void old_offset_assembly(void) // start offset assembly // (allows for block, so must be reentrant) -// TODO - maybe add a label argument to assign the block size afterwards (for assemble-to-end-address) (or add another pseudo opcode) static enum eos po_pseudopc(void) { struct number new_pc; // get new value - ALU_defined_int(&new_pc); // FIXME - allow for undefined! (complaining about non-addresses would be logical, but annoying) + ALU_defined_int(&new_pc); // complaining about non-addresses would be logical, but annoying /* TODO - add this. check if code can be shared with "*="! // check for modifiers while (parser_accept_comma()) { @@ -710,6 +709,10 @@ static enum eos po_pseudopc(void) } } */ + if (new_pc.val.intval < 0) { + Throw_error("Program counter cannot be negative."); + new_pc.val.intval = cpu_current_type->dummy_pc; + } pseudopc_start(&new_pc); // if there's a block, parse that and then restore old value! if (parse_optional_block()) { @@ -1573,6 +1576,10 @@ void notreallypo_setpc(void) // GotByte is '*' } } + if (intresult.val.intval < 0) { + Throw_error("Program counter cannot be negative."); + intresult.val.intval = cpu_current_type->dummy_pc; + } vcpu_set_pc(intresult.val.intval, segment_flags); // if wanted, perform "!outfilestart": diff --git a/src/version.h b/src/version.h index 9a18b77..b6f962a 100644 --- a/src/version.h +++ b/src/version.h @@ -9,7 +9,7 @@ #define RELEASE "0.97" // update before release FIXME #define CODENAME "Zem" // update before release -#define CHANGE_DATE "4 Aug" // update before release FIXME +#define CHANGE_DATE "5 Aug" // update before release FIXME #define CHANGE_YEAR "2024" // update before release //#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/" #define HOME_PAGE "http://sourceforge.net/p/acme-crossass/" // FIXME diff --git a/testing/auto/unpseudo.a b/testing/auto/unpseudo.a index e207f90..3ff3446 100644 --- a/testing/auto/unpseudo.a +++ b/testing/auto/unpseudo.a @@ -24,3 +24,18 @@ label2 nop !if does_not_make_sense != $1001 { !error "dnms is not $1001" } !if &does_not_make_sense != $1d02 { !error "dnms is not $1d02" } + + ; older versions automatically wrapped the pseudopc to zero, I now + ; realize this should be considered a bug: + !pseudopc $fffe { + !by 0, 1, 2, 3 +label3 } + !if label3 != $10002 { + !error "pseudopc seems to wrap to zero after $ffff" + } + ; and even worse, older versions automatically wrapped the outbuf index + ; to zero as well, this is now also considered a bug: + *=$10000 ; enter "second bank" + !if * == 0 { + !error "pc seems to wrap to zero after $ffff" + } diff --git a/testing/cliargs/dialect097.a b/testing/cliargs/dialect097.a index 943f97a..2c34288 100644 --- a/testing/cliargs/dialect097.a +++ b/testing/cliargs/dialect097.a @@ -4,10 +4,3 @@ !if depth_given_in_included_file != 0 { !error "included wrong file." } - ; program counter no longer silently wraps to zero beginning with 0.98: - !pseudopc $fffe { - !by 0, 1, 2, 3 -label } - !if label != 2 { - !error "program counter does not wrap to zero" - } diff --git a/testing/errors/pcnegative1.a b/testing/errors/pcnegative1.a new file mode 100644 index 0000000..9f798cb --- /dev/null +++ b/testing/errors/pcnegative1.a @@ -0,0 +1,2 @@ + + *=-3 ; -> "Program counter cannot be negative." diff --git a/testing/errors/pcnegative2.a b/testing/errors/pcnegative2.a new file mode 100644 index 0000000..b567959 --- /dev/null +++ b/testing/errors/pcnegative2.a @@ -0,0 +1,4 @@ + + *=$1000 + !pseudopc -17 { ; -> "Program counter cannot be negative." + } diff --git a/testing/errors/pcundefined1.a b/testing/errors/pcundefined1.a new file mode 100644 index 0000000..91902a7 --- /dev/null +++ b/testing/errors/pcundefined1.a @@ -0,0 +1,2 @@ + + nop ; -> "Program counter undefined." diff --git a/testing/errors/pcundefined2.a b/testing/errors/pcundefined2.a new file mode 100644 index 0000000..2608382 --- /dev/null +++ b/testing/errors/pcundefined2.a @@ -0,0 +1,3 @@ + + !pseudopc $8000 { ; -> "Program counter undefined." + } diff --git a/testing/errors/pcundefined3.a b/testing/errors/pcundefined3.a new file mode 100644 index 0000000..765bb88 --- /dev/null +++ b/testing/errors/pcundefined3.a @@ -0,0 +1,2 @@ + + !outfilestart ; -> "Program counter undefined." diff --git a/testing/errors/pcundefined4.a b/testing/errors/pcundefined4.a new file mode 100644 index 0000000..6ea147a --- /dev/null +++ b/testing/errors/pcundefined4.a @@ -0,0 +1,2 @@ + + !outfilelimit ; -> "Program counter undefined."