lawless-legends/Platform/Apple/tools/ACME/src/cpu.c

320 lines
9.0 KiB
C

//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// CPU stuff
#include "alu.h"
#include "config.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "mnemo.h"
#include "output.h"
#include "tree.h"
// Structure for linked list of segment data
typedef struct segment_t segment_t;
struct segment_t {
segment_t* next;
value_t start;
value_t length;
};
// Constants
static cpu_t CPU_6502 = {keyword_is_6502mnemo, CPU_FLAG_INDJMP_BUGGY};
static cpu_t CPU_6510 = {keyword_is_6510mnemo, CPU_FLAG_INDJMP_BUGGY};
static cpu_t CPU_65c02 = {keyword_is_65c02mnemo, 0};
//static cpu_t CPU_Rockwell65c02 = {keyword_is_Rockwell65c02mnemo, 0};
//static cpu_t CPU_WDC65c02 = {keyword_is_WDC65c02mnemo, 0};
static cpu_t CPU_65816 = {
keyword_is_65816mnemo,
CPU_FLAG_LONG_REGS | CPU_FLAG_IMM16
};
// Variables
cpu_t *CPU_now; // Struct of current CPU type (default 6502)
bool CPU65816_long_a;// Flag for long accumulator (default off)
bool CPU65816_long_r;// Flag for long index registers (default off)
value_t CPU_pc; // (Pseudo) program counter at start of statement
static segment_t* segment_list; // linked list of segment structures
// predefined stuff
static node_t* CPU_tree = NULL;// tree to hold CPU types
static node_t CPUs[] = {
// PREDEFNODE("z80", &CPU_Z80),
PREDEFNODE("6502", &CPU_6502),
PREDEFNODE("6510", &CPU_6510),
PREDEFNODE("65c02", &CPU_65c02),
// PREDEFNODE("Rockwell65c02", &CPU_Rockwell65c02),
// PREDEFNODE("WDC65c02", &CPU_WDC65c02),
PREDEFLAST("65816", &CPU_65816),
// ^^^^ this marks the last element
};
// init lowest and highest address
static void init_borders(value_t first_pc) {
Mem_lowest_pc = first_pc;
Mem_highest_pc = first_pc;
}
// set new program counter value
static void set_new_pc(value_t new_pc) {
segment_start = new_pc;
CPU_pc = new_pc;
Mem_current_pc = new_pc;
}
// Link segment data into segment chain
static void link_segment(value_t start, value_t length) {
segment_t *new_segment;
// may be faster if list is ordered!
new_segment = ALLOC_PASS(sizeof(segment_t));
new_segment->start = start;
new_segment->length = length;
new_segment->next = segment_list;
segment_list = new_segment;
}
// Show start and end of current segment
void CPU_end_segment(void) {
if(Mem_current_pc != CPU_pc)
Throw_warning("Offset assembly still active at end of segment. Switched it off.");
if(pass_flags & PASS_ISFIRST) {
link_segment(segment_start, Mem_current_pc - segment_start);
if(Process_verbosity > 1)
printf("Segment size is %ld ($%lx) bytes ($%lx - $%lx exclusive).\n", Mem_current_pc - segment_start, Mem_current_pc - segment_start, segment_start, Mem_current_pc);
}
}
// Set up new segment_max value according to the given program counter. Just
// find the next segment start and subtract 1.
void CPU_find_segment_max(value_t new_pc) {
segment_t* test_segment;
// may be faster if list is ordered!
segment_max = OUTBUFFERSIZE;// will be decremented later!
test_segment = segment_list;
while(test_segment) {
if((test_segment->start > new_pc)
&& (test_segment->start < segment_max))
segment_max = test_segment->start;
test_segment = test_segment->next;
}
segment_max--;// last free address available
}
// Check whether given PC is inside segment.
static void check_segment(value_t new_pc) {
segment_t* test_segment;
test_segment = segment_list;
while(test_segment) {
if((new_pc >= test_segment->start)
&& (new_pc < (test_segment->start) + (test_segment->length)))
Throw_warning("Segment starts inside another one, overwriting it.");
test_segment = test_segment->next;
}
}
// Parse (re-)definitions of program counter
void CPU_set_pc(void) {// Now GotByte = "*"
result_t new_pc;
NEXTANDSKIPSPACE(); // proceed with next char
// re-definitions of program counter change segment
if(GotByte == '=') {
GetByte();// proceed with next char
ALU_parse_expr_strict(&new_pc);
if(CPU_pc != PC_UNDEFINED) {
// It's a redefinition. Check some things:
// check whether new low
if(new_pc.value < Mem_lowest_pc)
Mem_lowest_pc = new_pc.value;
// show status of previous segment
CPU_end_segment();
// in first pass, maybe issue warning
if(pass_flags & PASS_ISFIRST) {
check_segment(new_pc.value);
CPU_find_segment_max(new_pc.value);
}
} else // it's the first pc definition
init_borders(new_pc.value);
set_new_pc(new_pc.value);
Input_ensure_EOS();
} else {
Throw_error(exception_syntax);
Input_skip_remainder();
}
}
// make sure PC is defined. If not, complain and set to dummy value
// FIXME - get rid of this function as it slows down Output_byte
inline void CPU_ensure_defined_pc(void) {
if(CPU_pc == PC_UNDEFINED) {
Throw_error("Program counter is unset.");
CPU_pc = PC_DUMMY;
}
}
// Insert byte until PC fits conditions
static enum eos_t PO_align(void) {
result_t and,
equal,
fill;
value_t test = CPU_pc;
CPU_ensure_defined_pc();
ALU_parse_expr_strict(&and);
fill.value = FILLVALUE_ALIGN;
if(!Input_accept_comma())
Throw_error(exception_syntax);
ALU_parse_expr_strict(&equal);
if(Input_accept_comma())
ALU_parse_expr_medium(&fill);
while((test++ & and.value) != equal.value)
Output_8b(fill.value);
return(ENSURE_EOS);
}
// Try to find CPU type held in DynaBuf. Returns whether succeeded.
bool CPU_find_cpu_struct(cpu_t** target) {
void* node_body;
if(!Tree_easy_scan(CPU_tree, &node_body, GlobalDynaBuf))
return(FALSE);
*target = node_body;
return(TRUE);
}
// Select CPU ("!cpu" pseudo opcode)
static enum eos_t PO_cpu(void) {
cpu_t* cpu_buffer = CPU_now; // remember current cpu
if(Input_read_and_lower_keyword())
if(!CPU_find_cpu_struct(&CPU_now))
Throw_error("Unknown processor.");
// If there's a block, parse that and then restore old value!
if(Parse_optional_block())
CPU_now = cpu_buffer;
return(ENSURE_EOS);
}
static const char Warning_old_offset_assembly[] =
"\"!pseudopc/!realpc\" is deprecated; use \"!pseudopc {}\" instead.";
// Start offset assembly
static enum eos_t PO_pseudopc(void) {
result_t result;
value_t offset = CPU_pc - Mem_current_pc;
// set new
ALU_parse_expr_strict(&result);
CPU_pc = result.value;
// If there's a block, parse that and then restore old value!
if(Parse_optional_block())
CPU_pc = (Mem_current_pc + offset) & 0xffff; // restore old
else if(pass_flags & PASS_ISFIRST)
Throw_warning(Warning_old_offset_assembly);
return(ENSURE_EOS);
}
// End offset assembly
static enum eos_t PO_realpc(void) {
if(pass_flags & PASS_ISFIRST)
Throw_warning(Warning_old_offset_assembly);
CPU_pc = Mem_current_pc;// deactivate offset assembly
return(ENSURE_EOS);
}
// If cpu type and value match, set register length variable to value.
// If cpu type and value don't match, complain instead.
static void check_and_set_reg_length(int *var, bool long_reg) {
if(long_reg && ((CPU_now->flags & CPU_FLAG_LONG_REGS) == 0))
Throw_error("Chosen CPU does not support long registers.");
else
*var = long_reg;
}
// Set register length, block-wise if needed.
static enum eos_t set_register_length(int* var, bool long_reg) {
int buffer = *var;
// Set new register length (or complain - whichever is more fitting)
check_and_set_reg_length(var, long_reg);
// If there's a block, parse that and then restore old value!
if(Parse_optional_block())
check_and_set_reg_length(var, buffer);// restore old length
return(ENSURE_EOS);
}
// Switch to long accu ("!al" pseudo opcode)
static enum eos_t PO_al(void) {
return(set_register_length(&CPU65816_long_a, TRUE));
}
// Switch to short accu ("!as" pseudo opcode)
static enum eos_t PO_as(void) {
return(set_register_length(&CPU65816_long_a, FALSE));
}
// Switch to long index registers ("!rl" pseudo opcode)
static enum eos_t PO_rl(void) {
return(set_register_length(&CPU65816_long_r, TRUE));
}
// Switch to short index registers ("!rs" pseudo opcode)
static enum eos_t PO_rs(void) {
return(set_register_length(&CPU65816_long_r, FALSE));
}
// pseudo opcode table
static node_t pseudo_opcodes[] = {
PREDEFNODE("align", PO_align),
PREDEFNODE("cpu", PO_cpu),
PREDEFNODE("pseudopc", PO_pseudopc),
PREDEFNODE("realpc", PO_realpc),
PREDEFNODE("al", PO_al),
PREDEFNODE("as", PO_as),
PREDEFNODE("rl", PO_rl),
PREDEFLAST("rs", PO_rs),
// ^^^^ this marks the last element
};
// Set default values for pass
void CPU_passinit(cpu_t* cpu_type, signed long starting_pc) {
// handle cpu type (default is 6502)
CPU_now = cpu_type ? cpu_type : &CPU_6502;
CPU65816_long_a = FALSE; // short accumulator
CPU65816_long_r = FALSE; // short index registers
// handle program counter
if(starting_pc == PC_UNDEFINED) {
init_borders(0); // set to _something_ (for !initmem)
segment_start = 0;
CPU_pc = PC_UNDEFINED;
Mem_current_pc = 0;
} else {
init_borders(starting_pc);
set_new_pc(starting_pc);
}
// other stuff
segment_list = NULL;
segment_max = OUTBUFFERSIZE-1;
}
// init cpu type tree (is done early)
void CPUtype_init(void) {
Tree_add_table(&CPU_tree, CPUs);
}
// init other stuff (done later)
void CPU_init(void) {
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}