357 lines
8.0 KiB
C
357 lines
8.0 KiB
C
#include "gbasm/gb_types.h"
|
|
#include "gbasm/opcodes.h"
|
|
#include "gbasm/errors.h"
|
|
|
|
#include "common/tri.h"
|
|
#include "common/common.h"
|
|
|
|
#define MAX_OPCODE_LEN 10 /*TODO: Check that's enough */
|
|
|
|
static bool opcodes_initted = false;
|
|
static struct tri opcode_tri;
|
|
|
|
static bool gbasm_argtype_in_set(uint32_t argtype_set, uint32_t argtype)
|
|
{
|
|
return !!(argtype & argtype_set);
|
|
}
|
|
|
|
static bool imm_is_8_bit(int val)
|
|
{
|
|
/* Allow signed or unsigned representation */
|
|
return (val <= 255) && (val >= -128);
|
|
}
|
|
|
|
static int check_no_args(const struct gbasm_parsed_inst *inst,
|
|
const struct gbasm_op_info *op_info)
|
|
{
|
|
if (inst->num_operands > 0) {
|
|
gbasm_too_many_args_error(inst, op_info);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t length_one_byte(
|
|
const struct gbasm_parsed_inst *inst,
|
|
const struct gbasm_op_info *info)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#define GEN_FIXED_EMITFN(_name, _data_array) \
|
|
static size_t _name(struct emitter *emitter, \
|
|
const struct gbasm_parsed_inst *inst) \
|
|
{ \
|
|
const uint8_t raw[] = _data_array; \
|
|
emit(emitter, raw, sizeof(raw)); \
|
|
return sizeof(raw); \
|
|
}
|
|
|
|
GEN_FIXED_EMITFN(nop_emit, {0x00})
|
|
GEN_FIXED_EMITFN(halt_emit, {0x76})
|
|
GEN_FIXED_EMITFN(stop_emit, {0x10})
|
|
GEN_FIXED_EMITFN(di_emit, {0xf3})
|
|
GEN_FIXED_EMITFN(ei_emit, {0xfb})
|
|
GEN_FIXED_EMITFN(rla_emit, {0x17})
|
|
GEN_FIXED_EMITFN(rra_emit, {0x1f})
|
|
GEN_FIXED_EMITFN(rlca_emit, {0x07})
|
|
GEN_FIXED_EMITFN(rrca_emit, {0x0f})
|
|
GEN_FIXED_EMITFN(daa_emit, {0x27})
|
|
GEN_FIXED_EMITFN(scf_emit, {0x37})
|
|
GEN_FIXED_EMITFN(reti_emit, {0xd9})
|
|
GEN_FIXED_EMITFN(cpl_emit, {0x2f})
|
|
GEN_FIXED_EMITFN(ccf_emit, {0x3f})
|
|
|
|
static int inc_dec_check(const struct gbasm_parsed_inst *inst,
|
|
const struct gbasm_op_info *op_info)
|
|
{
|
|
if (inst->num_operands > 1) {
|
|
gbasm_too_many_args_error(inst, op_info);
|
|
}
|
|
|
|
if (inst->num_operands < 1) {
|
|
gbasm_too_few_args_error(inst, op_info);
|
|
}
|
|
|
|
if (!gbasm_argtype_in_set(op_info->operand_types[0], inst->operands[0].type)) {
|
|
gbasm_arg_wrong_type_error(inst, op_info);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ld_check(const struct gbasm_parsed_inst *inst,
|
|
const struct gbasm_op_info *op_info)
|
|
{
|
|
if (inst->num_operands < 2) {
|
|
gbasm_too_many_args_error(inst, op_info);
|
|
}
|
|
|
|
if (inst->num_operands > 2) {
|
|
gbasm_too_few_args_error(inst, op_info);
|
|
}
|
|
|
|
if (!gbasm_argtype_in_set(op_info->operand_types[0], inst->operands[0].type)) {
|
|
gbasm_arg_wrong_type_error(inst, op_info);
|
|
}
|
|
|
|
if (inst->operands[0].type == GBASM_OPERAND_REG_8) {
|
|
if (!imm_is_8_bit(inst->operands[1].imm.value)) {
|
|
DEBUG_LOG("inst->operands[1].imm.value = %d",
|
|
inst->operands[1].imm.value);
|
|
|
|
gbasm_invalid_imm_error(inst, op_info);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t inc_emit(struct emitter *emitter,
|
|
const struct gbasm_parsed_inst *inst)
|
|
{
|
|
uint8_t opcode = 0x00;
|
|
|
|
switch (inst->operands[0].type) {
|
|
case GBASM_OPERAND_REG_8:
|
|
switch (inst->operands[0].r8.type) {
|
|
case GBASM_OPERAND_R8_A: opcode = 0x3c; break;
|
|
case GBASM_OPERAND_R8_B: opcode = 0x04; break;
|
|
case GBASM_OPERAND_R8_C: opcode = 0x0c; break;
|
|
case GBASM_OPERAND_R8_D: opcode = 0x14; break;
|
|
case GBASM_OPERAND_R8_E: opcode = 0x1c; break;
|
|
case GBASM_OPERAND_R8_H: opcode = 0x24; break;
|
|
case GBASM_OPERAND_R8_L: opcode = 0x2c; break;
|
|
case GBASM_OPERAND_R8_HL_DEREF: opcode = 0x34; break;
|
|
}
|
|
break;
|
|
case GBASM_OPERAND_REG_16:
|
|
switch (inst->operands[0].r16.type) {
|
|
case GBASM_OPERAND_R16_BC: opcode = 0x03; break;
|
|
case GBASM_OPERAND_R16_DE: opcode = 0x13; break;
|
|
case GBASM_OPERAND_R16_HL: opcode = 0x23; break;
|
|
case GBASM_OPERAND_R16_SP: opcode = 0x33; break;
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
|
|
emit(emitter, &opcode, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
size_t dec_emit(struct emitter *emitter,
|
|
const struct gbasm_parsed_inst *inst)
|
|
{
|
|
uint8_t opcode = 0x00;
|
|
|
|
switch (inst->operands[0].type) {
|
|
case GBASM_OPERAND_REG_8:
|
|
switch (inst->operands[0].r8.type) {
|
|
case GBASM_OPERAND_R8_A: opcode = 0x3d; break;
|
|
case GBASM_OPERAND_R8_B: opcode = 0x05; break;
|
|
case GBASM_OPERAND_R8_C: opcode = 0x0d; break;
|
|
case GBASM_OPERAND_R8_D: opcode = 0x15; break;
|
|
case GBASM_OPERAND_R8_E: opcode = 0x1d; break;
|
|
case GBASM_OPERAND_R8_H: opcode = 0x25; break;
|
|
case GBASM_OPERAND_R8_L: opcode = 0x2d; break;
|
|
case GBASM_OPERAND_R8_HL_DEREF: opcode = 0x35; break;
|
|
}
|
|
break;
|
|
case GBASM_OPERAND_REG_16:
|
|
switch (inst->operands[0].r16.type) {
|
|
case GBASM_OPERAND_R16_BC: opcode = 0x0b; break;
|
|
case GBASM_OPERAND_R16_DE: opcode = 0x1b; break;
|
|
case GBASM_OPERAND_R16_HL: opcode = 0x2b; break;
|
|
case GBASM_OPERAND_R16_SP: opcode = 0x3b; break;
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
|
|
emit(emitter, &opcode, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
size_t ld_emit(struct emitter *emitter,
|
|
const struct gbasm_parsed_inst *inst)
|
|
{
|
|
uint8_t opcode;
|
|
uint8_t imm8;
|
|
|
|
if (inst->operands[0].type == GBASM_OPERAND_REG_8) {
|
|
switch (inst->operands[1].type) {
|
|
case GBASM_OPERAND_REG_8:
|
|
opcode = 0x40;
|
|
opcode += inst->operands[0].r8.type * 8;
|
|
opcode += inst->operands[1].r8.type;
|
|
emit(emitter, &opcode, 1);
|
|
return 1;
|
|
case GBASM_OPERAND_IMM:
|
|
imm8 = inst->operands[1].imm.value;
|
|
opcode = 0x06;
|
|
opcode += inst->operands[0].r8.type * 8;
|
|
emit(emitter, &opcode, 1);
|
|
emit(emitter, &imm8, 1);
|
|
return 2;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
struct gbasm_op_info gbasm_op_infos[] = {
|
|
{
|
|
.opcode = "nop",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = nop_emit,
|
|
},
|
|
{
|
|
.opcode = "halt",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = halt_emit,
|
|
},
|
|
{
|
|
.opcode = "stop",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = stop_emit,
|
|
},
|
|
{
|
|
.opcode = "di",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = di_emit,
|
|
},
|
|
{
|
|
.opcode = "ei",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = ei_emit,
|
|
},
|
|
{
|
|
.opcode = "rla",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = rla_emit,
|
|
},
|
|
{
|
|
.opcode = "rra",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = rra_emit,
|
|
},
|
|
{
|
|
.opcode = "rlca",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = rlca_emit,
|
|
},
|
|
{
|
|
.opcode = "rrca",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = rrca_emit,
|
|
},
|
|
{
|
|
.opcode = "daa",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = daa_emit,
|
|
},
|
|
{
|
|
.opcode = "scf",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = scf_emit,
|
|
},
|
|
{
|
|
.opcode = "reti",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = reti_emit,
|
|
},
|
|
{
|
|
.opcode = "cpl",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = cpl_emit,
|
|
},
|
|
{
|
|
.opcode = "ccf",
|
|
.operand_types = {0},
|
|
.check = check_no_args,
|
|
.length = length_one_byte,
|
|
.emit = ccf_emit,
|
|
},
|
|
{
|
|
.opcode = "inc",
|
|
/* TODO: support inc (HL) */
|
|
.operand_types = { GBASM_OPERAND_REG_8 | GBASM_OPERAND_REG_16 },
|
|
.check = inc_dec_check,
|
|
.length = length_one_byte,
|
|
.emit = inc_emit,
|
|
},
|
|
{
|
|
.opcode = "dec",
|
|
/* TODO: support inc (HL) */
|
|
.operand_types = { GBASM_OPERAND_REG_8 | GBASM_OPERAND_REG_16 },
|
|
.check = inc_dec_check,
|
|
.length = length_one_byte,
|
|
.emit = dec_emit,
|
|
},
|
|
{
|
|
.opcode = "ld",
|
|
/* support all of the other operands */
|
|
.operand_types = { GBASM_OPERAND_REG_8,
|
|
GBASM_OPERAND_REG_8 |
|
|
GBASM_OPERAND_IMM },
|
|
.check = ld_check,
|
|
.length = length_one_byte,
|
|
.emit = ld_emit,
|
|
},
|
|
|
|
};
|
|
|
|
static void init_opcode_tri()
|
|
{
|
|
int i;
|
|
tri_init(&opcode_tri);
|
|
opcodes_initted = true;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(gbasm_op_infos); i++) {
|
|
tri_add_string(&opcode_tri, gbasm_op_infos[i].opcode, &gbasm_op_infos[i]);
|
|
}
|
|
}
|
|
|
|
const struct gbasm_op_info *gbasm_get_opcode_info(const char *opcode)
|
|
{
|
|
if (!opcodes_initted) {
|
|
init_opcode_tri();
|
|
}
|
|
|
|
return tri_get_string(&opcode_tri, opcode);
|
|
}
|