finally removed the 64 KiB limit, the outbuffer size is now determined at

runtime. I added a hard limit of 64 MiB, but that value is completely
arbitrary.


git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@402 4df02467-bbd4-4a76-a152-e7ce94205b78
This commit is contained in:
marcobaye 2024-08-27 18:54:07 +00:00
parent d0b1ad84b7
commit be4580af53
20 changed files with 184 additions and 138 deletions

View File

@ -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).

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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 {

View File

@ -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"
};

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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":

View File

@ -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

View File

@ -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"
}

View File

@ -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"
}

View File

@ -0,0 +1,2 @@
*=-3 ; -> "Program counter cannot be negative."

View File

@ -0,0 +1,4 @@
*=$1000
!pseudopc -17 { ; -> "Program counter cannot be negative."
}

View File

@ -0,0 +1,2 @@
nop ; -> "Program counter undefined."

View File

@ -0,0 +1,3 @@
!pseudopc $8000 { ; -> "Program counter undefined."
}

View File

@ -0,0 +1,2 @@
!outfilestart ; -> "Program counter undefined."

View File

@ -0,0 +1,2 @@
!outfilelimit ; -> "Program counter undefined."