mirror of
https://github.com/uffejakobsen/acme.git
synced 2024-11-21 11:32:23 +00:00
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:
parent
d0b1ad84b7
commit
be4580af53
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
13
src/acme.c
13
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)
|
||||
|
@ -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 {
|
||||
|
30
src/cpu.c
30
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"
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
187
src/output.c
187
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
|
||||
|
@ -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":
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
2
testing/errors/pcnegative1.a
Normal file
2
testing/errors/pcnegative1.a
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
*=-3 ; -> "Program counter cannot be negative."
|
4
testing/errors/pcnegative2.a
Normal file
4
testing/errors/pcnegative2.a
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
*=$1000
|
||||
!pseudopc -17 { ; -> "Program counter cannot be negative."
|
||||
}
|
2
testing/errors/pcundefined1.a
Normal file
2
testing/errors/pcundefined1.a
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
nop ; -> "Program counter undefined."
|
3
testing/errors/pcundefined2.a
Normal file
3
testing/errors/pcundefined2.a
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
!pseudopc $8000 { ; -> "Program counter undefined."
|
||||
}
|
2
testing/errors/pcundefined3.a
Normal file
2
testing/errors/pcundefined3.a
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
!outfilestart ; -> "Program counter undefined."
|
2
testing/errors/pcundefined4.a
Normal file
2
testing/errors/pcundefined4.a
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
!outfilelimit ; -> "Program counter undefined."
|
Loading…
Reference in New Issue
Block a user