3092 lines · 98.3 KB · C
RawDownload
/* *  ARM specific functions for TCC assembler * *  Copyright (c) 2001, 2002 Fabrice Bellard *  Copyright (c) 2020 Danny Milosavljevic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */ #ifdef TARGET_DEFS_ONLY #define CONFIG_TCC_ASM#define NB_ASM_REGS 16 ST_FUNC void g(int c);ST_FUNC void gen_le16(int c);ST_FUNC void gen_le32(int c); /*************************************************************/#else/*************************************************************/ #define USING_GLOBALS#include "tcc.h" enum {    OPT_REG32,    OPT_REGSET32,    OPT_IM8,    OPT_IM8N,    OPT_IM32,    OPT_VREG32,    OPT_VREG64,};#define OP_REG32  (1 << OPT_REG32)#define OP_VREG32 (1 << OPT_VREG32)#define OP_VREG64 (1 << OPT_VREG64)#define OP_REG    (OP_REG32 | OP_VREG32 | OP_VREG64)#define OP_IM32   (1 << OPT_IM32)#define OP_IM8   (1 << OPT_IM8)#define OP_IM8N   (1 << OPT_IM8N)#define OP_REGSET32  (1 << OPT_REGSET32) typedef struct Operand {    uint32_t type;    union {        uint8_t reg;        uint16_t regset;        ExprValue e;    };} Operand; /* Read the VFP register referred to by token T.   If OK, returns its number.   If not OK, returns -1. */static int asm_parse_vfp_regvar(int t, int double_precision){    if (double_precision) {        if (t >= TOK_ASM_d0 && t <= TOK_ASM_d15)            return t - TOK_ASM_d0;    } else {        if (t >= TOK_ASM_s0 && t <= TOK_ASM_s31)            return t - TOK_ASM_s0;    }    return -1;} /* Parse a text containing operand and store the result in OP */static void parse_operand(TCCState *s1, Operand *op){    ExprValue e;    int8_t reg;    uint16_t regset = 0;     op->type = 0;     if (tok == '{') { // regset literal        next(); // skip '{'        while (tok != '}' && tok != TOK_EOF) {            reg = asm_parse_regvar(tok);            if (reg == -1) {                expect("register");            } else                next(); // skip register name             if ((1 << reg) < regset)                tcc_warning("registers will be processed in ascending order by hardware--but are not specified in ascending order here");            regset |= 1 << reg;            if (tok != ',')                break;            next(); // skip ','        }        skip('}');        if (regset == 0) {            // ARM instructions don't support empty regset.            tcc_error("empty register list is not supported");        } else {            op->type = OP_REGSET32;            op->regset = regset;        }        return;    } else if ((reg = asm_parse_regvar(tok)) != -1) {        next(); // skip register name        op->type = OP_REG32;        op->reg = (uint8_t) reg;        return;    } else if ((reg = asm_parse_vfp_regvar(tok, 0)) != -1) {        next(); // skip register name        op->type = OP_VREG32;        op->reg = (uint8_t) reg;        return;    } else if ((reg = asm_parse_vfp_regvar(tok, 1)) != -1) {        next(); // skip register name        op->type = OP_VREG64;        op->reg = (uint8_t) reg;        return;    } else if (tok == '#' || tok == '$') {        /* constant value */        next(); // skip '#' or '$'    }    asm_expr(s1, &e);    op->type = OP_IM32;    op->e = e;    if (!op->e.sym) {        if ((int) op->e.v < 0 && (int) op->e.v >= -255)            op->type = OP_IM8N;        else if (op->e.v == (uint8_t)op->e.v)            op->type = OP_IM8;    } else        expect("operand");} /* XXX: make it faster ? */ST_FUNC void g(int c){    int ind1;    if (nocode_wanted)        return;    ind1 = ind + 1;    if (ind1 > cur_text_section->data_allocated)        section_realloc(cur_text_section, ind1);    cur_text_section->data[ind] = c;    ind = ind1;} ST_FUNC void gen_le16 (int i){    g(i);    g(i>>8);} ST_FUNC void gen_le32 (int i){    int ind1;    if (nocode_wanted)        return;    ind1 = ind + 4;    if (ind1 > cur_text_section->data_allocated)        section_realloc(cur_text_section, ind1);    cur_text_section->data[ind++] = i & 0xFF;    cur_text_section->data[ind++] = (i >> 8) & 0xFF;    cur_text_section->data[ind++] = (i >> 16) & 0xFF;    cur_text_section->data[ind++] = (i >> 24) & 0xFF;} ST_FUNC void gen_expr32(ExprValue *pe){    gen_le32(pe->v);} static uint32_t condition_code_of_token(int token) {    if (token < TOK_ASM_nopeq) {        expect("condition-enabled instruction");    } else        return (token - TOK_ASM_nopeq) & 15;} static void asm_emit_opcode(int token, uint32_t opcode) {    gen_le32((condition_code_of_token(token) << 28) | opcode);} static void asm_emit_unconditional_opcode(uint32_t opcode) {    gen_le32(opcode);} static void asm_emit_coprocessor_opcode(uint32_t high_nibble, uint8_t cp_number, uint8_t cp_opcode, uint8_t cp_destination_register, uint8_t cp_n_operand_register, uint8_t cp_m_operand_register, uint8_t cp_opcode2, int inter_processor_transfer){    uint32_t opcode = 0xe000000;    if (inter_processor_transfer)        opcode |= 1 << 4;    //assert(cp_opcode < 16);    opcode |= cp_opcode << 20;    //assert(cp_n_operand_register < 16);    opcode |= cp_n_operand_register << 16;    //assert(cp_destination_register < 16);    opcode |= cp_destination_register << 12;    //assert(cp_number < 16);    opcode |= cp_number << 8;    //assert(cp_information < 8);    opcode |= cp_opcode2 << 5;    //assert(cp_m_operand_register < 16);    opcode |= cp_m_operand_register;    asm_emit_unconditional_opcode((high_nibble << 28) | opcode);} static void asm_nullary_opcode(int token){    switch (ARM_INSTRUCTION_GROUP(token)) {    case TOK_ASM_nopeq:        asm_emit_opcode(token, 0xd << 21); // mov r0, r0        break;    case TOK_ASM_wfeeq:        asm_emit_opcode(token, 0x320f002);    case TOK_ASM_wfieq:        asm_emit_opcode(token, 0x320f003);        break;    default:        expect("nullary instruction");    }} static void asm_unary_opcode(TCCState *s1, int token){    Operand op;    parse_operand(s1, &op);     switch (ARM_INSTRUCTION_GROUP(token)) {    case TOK_ASM_swieq:    case TOK_ASM_svceq:        if (op.type != OP_IM8)            expect("immediate 8-bit unsigned integer");        else {            /* Note: Dummy operand (ignored by processor): ARM ref documented 0...255, ARM instruction set documented 24 bit */            asm_emit_opcode(token, (0xf << 24) | op.e.v);        }        break;    default:        expect("unary instruction");    }} static void asm_binary_opcode(TCCState *s1, int token){    Operand ops[2];    Operand rotation;    uint32_t encoded_rotation = 0;    uint64_t amount;    parse_operand(s1, &ops[0]);    skip(',');    parse_operand(s1, &ops[1]);    if (ops[0].type != OP_REG32) {        expect("(destination operand) register");    }     if (ops[0].reg == 15) {        tcc_error("'%s' does not support 'pc' as operand", get_tok_str(token, NULL));    }     if (ops[0].reg == 13)        tcc_warning("Using 'sp' as operand with '%s' is deprecated by ARM", get_tok_str(token, NULL));     if (ops[1].type != OP_REG32) {        switch (ARM_INSTRUCTION_GROUP(token)) {        case TOK_ASM_movteq:        case TOK_ASM_movweq:            if (ops[1].type == OP_IM8 || ops[1].type == OP_IM8N || ops[1].type == OP_IM32) {                if (ops[1].e.v >= 0 && ops[1].e.v <= 0xFFFF) {                    uint16_t immediate_value = ops[1].e.v;                    switch (ARM_INSTRUCTION_GROUP(token)) {                    case TOK_ASM_movteq:                        asm_emit_opcode(token, 0x3400000 | (ops[0].reg << 12) | (immediate_value & 0xF000) << 4 | (immediate_value & 0xFFF));                        break;                    case TOK_ASM_movweq:                        asm_emit_opcode(token, 0x3000000 | (ops[0].reg << 12) | (immediate_value & 0xF000) << 4 | (immediate_value & 0xFFF));                        break;                    }                } else                    expect("(source operand) immediate 16 bit value");            } else                expect("(source operand) immediate");            break;        default:            expect("(source operand) register");        }        return;    }     if (ops[1].reg == 15) {        tcc_error("'%s' does not support 'pc' as operand", get_tok_str(token, NULL));    }     if (ops[1].reg == 13)        tcc_warning("Using 'sp' as operand with '%s' is deprecated by ARM", get_tok_str(token, NULL));     if (tok == ',') {        next(); // skip ','        if (tok == TOK_ASM_ror) {            next(); // skip 'ror'            parse_operand(s1, &rotation);            if (rotation.type != OP_IM8) {                expect("immediate value for rotation");            } else {                amount = rotation.e.v;                switch (amount) {                case 8:                    encoded_rotation = 1 << 10;                    break;                case 16:                    encoded_rotation = 2 << 10;                    break;                case 24:                    encoded_rotation = 3 << 10;                    break;                default:                    expect("'8' or '16' or '24'");                }            }        }    }    switch (ARM_INSTRUCTION_GROUP(token)) {    case TOK_ASM_clzeq:        if (encoded_rotation)            tcc_error("clz does not support rotation");        asm_emit_opcode(token, 0x16f0f10 | (ops[0].reg << 12) | ops[1].reg);        break;    case TOK_ASM_sxtbeq:        asm_emit_opcode(token, 0x6af0070 | (ops[0].reg << 12) | ops[1].reg | encoded_rotation);        break;    case TOK_ASM_sxtheq:        asm_emit_opcode(token, 0x6bf0070 | (ops[0].reg << 12) | ops[1].reg | encoded_rotation);        break;    case TOK_ASM_uxtbeq:        asm_emit_opcode(token, 0x6ef0070 | (ops[0].reg << 12) | ops[1].reg | encoded_rotation);        break;    case TOK_ASM_uxtheq:        asm_emit_opcode(token, 0x6ff0070 | (ops[0].reg << 12) | ops[1].reg | encoded_rotation);        break;    default:        expect("binary instruction");    }} static void asm_coprocessor_opcode(TCCState *s1, int token) {    uint8_t coprocessor;    Operand opcode1;    Operand opcode2;    uint8_t registers[3];    unsigned int i;    uint8_t high_nibble;    uint8_t mrc = 0;     if (tok >= TOK_ASM_p0 && tok <= TOK_ASM_p15) {        coprocessor = tok - TOK_ASM_p0;        next();    } else {        expect("'p<number>'");    }    skip(',');    parse_operand(s1, &opcode1);    if (opcode1.type != OP_IM8 || opcode1.e.v > 15) {        tcc_error("opcode1 of instruction '%s' must be an immediate value between 0 and 15", get_tok_str(token, NULL));    }     for (i = 0; i < 3; ++i) {        skip(',');        if (i == 0 && token != TOK_ASM_cdp2 && (ARM_INSTRUCTION_GROUP(token) == TOK_ASM_mrceq || ARM_INSTRUCTION_GROUP(token) == TOK_ASM_mcreq)) {            if (tok >= TOK_ASM_r0 && tok <= TOK_ASM_pc) {                registers[i] = asm_parse_regvar(tok);                next();            } else {                expect("'r<number>'");            }        } else {            if (tok >= TOK_ASM_c0 && tok <= TOK_ASM_c15) {                registers[i] = tok - TOK_ASM_c0;                next();            } else {                expect("'c<number>'");            }        }    }    if (tok == ',') {        next();        parse_operand(s1, &opcode2);    } else {        opcode2.type = OP_IM8;        opcode2.e.v = 0;    }    if (opcode2.type != OP_IM8 || opcode2.e.v > 15) {        tcc_error("opcode2 of instruction '%s' must be an immediate value between 0 and 15", get_tok_str(token, NULL));    }     if (token == TOK_ASM_cdp2) {        high_nibble = 0xF;        asm_emit_coprocessor_opcode(high_nibble, coprocessor, opcode1.e.v, registers[0], registers[1], registers[2], opcode2.e.v, 0);        return;    } else        high_nibble = condition_code_of_token(token);     switch (ARM_INSTRUCTION_GROUP(token)) {    case TOK_ASM_cdpeq:        asm_emit_coprocessor_opcode(high_nibble, coprocessor, opcode1.e.v, registers[0], registers[1], registers[2], opcode2.e.v, 0);        break;    case TOK_ASM_mrceq:        // opcode1 encoding changes! highest and lowest bit gone.        mrc = 1;        /* fallthrough */    case TOK_ASM_mcreq:        // opcode1 encoding changes! highest and lowest bit gone.        if (opcode1.e.v > 7) {            tcc_error("opcode1 of instruction '%s' must be an immediate value between 0 and 7", get_tok_str(token, NULL));        }        asm_emit_coprocessor_opcode(high_nibble, coprocessor, (opcode1.e.v << 1) | mrc, registers[0], registers[1], registers[2], opcode2.e.v, 1);        break;    default:        expect("known instruction");    }} /* data processing and single data transfer instructions only */#define ENCODE_RN(register_index) ((register_index) << 16)#define ENCODE_RD(register_index) ((register_index) << 12)#define ENCODE_SET_CONDITION_CODES (1 << 20) /* Note: For data processing instructions, "1" means immediate.   Note: For single data transfer instructions, "0" means immediate. */#define ENCODE_IMMEDIATE_FLAG (1 << 25) #define ENCODE_BARREL_SHIFTER_SHIFT_BY_REGISTER (1 << 4)#define ENCODE_BARREL_SHIFTER_MODE_LSL (0 << 5)#define ENCODE_BARREL_SHIFTER_MODE_LSR (1 << 5)#define ENCODE_BARREL_SHIFTER_MODE_ASR (2 << 5)#define ENCODE_BARREL_SHIFTER_MODE_ROR (3 << 5)#define ENCODE_BARREL_SHIFTER_REGISTER(register_index) ((register_index) << 8)#define ENCODE_BARREL_SHIFTER_IMMEDIATE(value) ((value) << 7) static void asm_block_data_transfer_opcode(TCCState *s1, int token){    uint32_t opcode;    int op0_exclam = 0;    Operand ops[2];    int nb_ops = 1;    parse_operand(s1, &ops[0]);    if (tok == '!') {        op0_exclam = 1;        next(); // skip '!'    }    if (tok == ',') {        next(); // skip comma        parse_operand(s1, &ops[1]);        ++nb_ops;    }    if (nb_ops < 1) {        expect("at least one operand");    } else if (ops[nb_ops - 1].type != OP_REGSET32) {        expect("(last operand) register list");    }     // block data transfer: 1 0 0 P U S W L << 20 (general case):    // operands:    //   Rn: bits 19...16 base register    //   Register List: bits 15...0     switch (ARM_INSTRUCTION_GROUP(token)) {    case TOK_ASM_pusheq: // TODO: Optimize 1-register case to: str ?, [sp, #-4]!        // Instruction: 1 I=0 P=1 U=0 S=0 W=1 L=0 << 20, op 1101        //   operands:        //      Rn: base register        //      Register List: bits 15...0        if (nb_ops != 1)            expect("exactly one operand");        else            asm_emit_opcode(token, (0x92d << 16) | ops[0].regset); // TODO: base register ?        break;    case TOK_ASM_popeq: // TODO: Optimize 1-register case to: ldr ?, [sp], #4        // Instruction: 1 I=0 P=0 U=1 S=0 W=0 L=1 << 20, op 1101        //   operands:        //      Rn: base register        //      Register List: bits 15...0        if (nb_ops != 1)            expect("exactly one operand");        else            asm_emit_opcode(token, (0x8bd << 16) | ops[0].regset); // TODO: base register ?        break;    case TOK_ASM_stmdaeq:    case TOK_ASM_ldmdaeq:    case TOK_ASM_stmeq:    case TOK_ASM_ldmeq:

Lines 1–500 of 3092.