[PATCH 0/3] Add integration of C operands to ARM inline assembler

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

[PATCH 0/3] Add integration of C operands to ARM inline assembler

Danny Milosavljevic
This patchset adds support for C operands as input/output of ARM inline
assembly blocks.

Danny Milosavljevic (3):
  arm-asm: Implement asm_gen_code
  arm-asm: Implement asm_compute_constraints
  arm-asm: Implement subst_asm_operand

 arm-asm.c | 442 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 438 insertions(+), 4 deletions(-)


_______________________________________________
Tinycc-devel mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel
Reply | Threaded
Open this post in threaded view
|

[PATCH 1/3] arm-asm: Implement asm_gen_code

Danny Milosavljevic
---
 arm-asm.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 80 insertions(+), 3 deletions(-)

diff --git a/arm-asm.c b/arm-asm.c
index 1aeb28c..d0c3de1 100644
--- a/arm-asm.c
+++ b/arm-asm.c
@@ -1214,10 +1214,87 @@ ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier)
 
 /* generate prolog and epilog code for asm statement */
 ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands,
-                         int nb_outputs, int is_output,
-                         uint8_t *clobber_regs,
-                         int out_reg)
+                          int nb_outputs, int is_output,
+                          uint8_t *clobber_regs,
+                          int out_reg)
 {
+    uint8_t regs_allocated[NB_ASM_REGS];
+    ASMOperand *op;
+    int i, reg;
+    uint32_t saved_regset = 0;
+
+    // TODO: Check non-E ABI.
+    // Note: Technically, r13 (sp) is also callee-saved--but that does not matter yet
+    static uint8_t reg_saved[] = { 4, 5, 6, 7, 8, 9 /* Note: sometimes special reg "sb" */ , 10, 11 };
+
+    /* mark all used registers */
+    memcpy(regs_allocated, clobber_regs, sizeof(regs_allocated));
+    for(i = 0; i < nb_operands;i++) {
+        op = &operands[i];
+        if (op->reg >= 0)
+            regs_allocated[op->reg] = 1;
+    }
+    for(i = 0; i < sizeof(reg_saved)/sizeof(reg_saved[0]); i++) {
+        reg = reg_saved[i];
+        if (regs_allocated[reg])
+            saved_regset |= 1 << reg;
+    }
+
+    if (!is_output) { // prolog
+        /* generate reg save code */
+        if (saved_regset)
+            gen_le32(0xe92d0000 | saved_regset); // push {...}
+
+        /* generate load code */
+        for(i = 0; i < nb_operands; i++) {
+            op = &operands[i];
+            if (op->reg >= 0) {
+                if ((op->vt->r & VT_VALMASK) == VT_LLOCAL &&
+                    op->is_memory) {
+                    /* memory reference case (for both input and
+                       output cases) */
+                    SValue sv;
+                    sv = *op->vt;
+                    sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL | VT_LVAL;
+                    sv.type.t = VT_PTR;
+                    load(op->reg, &sv);
+                } else if (i >= nb_outputs || op->is_rw) { // not write-only
+                    /* load value in register */
+                    load(op->reg, op->vt);
+                    if (op->is_llong)
+                        tcc_error("long long not implemented");
+                }
+            }
+        }
+    } else { // epilog
+        /* generate save code */
+        for(i = 0 ; i < nb_outputs; i++) {
+            op = &operands[i];
+            if (op->reg >= 0) {
+                if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) {
+                    if (!op->is_memory) {
+                        SValue sv;
+                        sv = *op->vt;
+                        sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL;
+                        sv.type.t = VT_PTR;
+                        load(out_reg, &sv);
+
+                        sv = *op->vt;
+                        sv.r = (sv.r & ~VT_VALMASK) | out_reg;
+                        store(op->reg, &sv);
+                    }
+                } else {
+                    store(op->reg, op->vt);
+                    if (op->is_llong)
+                        tcc_error("long long not implemented");
+                }
+            }
+        }
+
+        /* generate reg restore code */
+        if (saved_regset)
+            gen_le32(0xe8bd0000 | saved_regset); // pop {...}
+    }
 }
 
 ST_FUNC void asm_compute_constraints(ASMOperand *operands,

_______________________________________________
Tinycc-devel mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel
Reply | Threaded
Open this post in threaded view
|

[PATCH 2/3] arm-asm: Implement asm_compute_constraints

Danny Milosavljevic
In reply to this post by Danny Milosavljevic
---
 arm-asm.c | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 286 insertions(+)

diff --git a/arm-asm.c b/arm-asm.c
index d0c3de1..881a6b9 100644
--- a/arm-asm.c
+++ b/arm-asm.c
@@ -1297,11 +1297,297 @@ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands,
     }
 }
 
+/* return the constraint priority (we allocate first the lowest
+   numbered constraints) */
+static inline int constraint_priority(const char *str)
+{
+    int priority, c, pr;
+
+    /* we take the lowest priority */
+    priority = 0;
+    for(;;) {
+        c = *str;
+        if (c == '\0')
+            break;
+        str++;
+        switch(c) {
+        case 'l': // in ARM mode, that's  an alias for 'r' [ARM].
+        case 'r': // register [general]
+        case 'p': // valid memory address for load,store [general]
+            pr = 3;
+            break;
+        case 'M': // integer constant for shifts [ARM]
+        case 'I': // integer valid for data processing instruction immediate
+        case 'J': // integer in range -4095...4095
+
+        case 'i': // immediate integer operand, including symbolic constants [general]
+        case 'm': // memory operand [general]
+        case 'g': // general-purpose-register, memory, immediate integer [general]
+            pr = 4;
+            break;
+        default:
+            tcc_error("unknown constraint '%c'", c);
+            pr = 0;
+        }
+        if (pr > priority)
+            priority = pr;
+    }
+    return priority;
+}
+
+static const char *skip_constraint_modifiers(const char *p)
+{
+    /* Constraint modifier:
+        =   Operand is written to by this instruction
+        +   Operand is both read and written to by this instruction
+        %   Instruction is commutative for this operand and the following operand.
+
+       Per-alternative constraint modifier:
+        &   Operand is clobbered before the instruction is done using the input operands
+    */
+    while (*p == '=' || *p == '&' || *p == '+' || *p == '%')
+        p++;
+    return p;
+}
+
+#define REG_OUT_MASK 0x01
+#define REG_IN_MASK  0x02
+
+#define is_reg_allocated(reg) (regs_allocated[reg] & reg_mask)
+
 ST_FUNC void asm_compute_constraints(ASMOperand *operands,
                                     int nb_operands, int nb_outputs,
                                     const uint8_t *clobber_regs,
                                     int *pout_reg)
 {
+    /* overall format: modifier, then ,-seperated list of alternatives; all operands for a single instruction must have the same number of alternatives */
+    /* TODO: Simple constraints
+        whitespace  ignored
+        o  memory operand that is offsetable
+        V  memory but not offsetable
+        <  memory operand with autodecrement addressing is allowed.  Restrictions apply.
+        >  memory operand with autoincrement addressing is allowed.  Restrictions apply.
+        n  immediate integer operand with a known numeric value
+        E  immediate floating operand (const_double) is allowed, but only if target=host
+        F  immediate floating operand (const_double or const_vector) is allowed
+        s  immediate integer operand whose value is not an explicit integer
+        X  any operand whatsoever
+        0...9 (postfix); (can also be more than 1 digit number);  an operand that matches the specified operand number is allowed
+    */
+
+    /* TODO: ARM constraints:
+        k the stack pointer register
+        G the floating-point constant 0.0
+        Q memory reference where the exact address is in a single register ("m" is preferable for asm statements)
+        R an item in the constant pool
+        S symbol in the text segment of the current file
+[       Uv memory reference suitable for VFP load/store insns (reg+constant offset)]
+[       Uy memory reference suitable for iWMMXt load/store instructions]
+        Uq memory reference suitable for the ARMv4 ldrsb instruction
+    */
+    ASMOperand *op;
+    int sorted_op[MAX_ASM_OPERANDS];
+    int i, j, k, p1, p2, tmp, reg, c, reg_mask;
+    const char *str;
+    uint8_t regs_allocated[NB_ASM_REGS];
+
+    /* init fields */
+    for (i = 0; i < nb_operands; i++) {
+        op = &operands[i];
+        op->input_index = -1;
+        op->ref_index = -1;
+        op->reg = -1;
+        op->is_memory = 0;
+        op->is_rw = 0;
+    }
+    /* compute constraint priority and evaluate references to output
+       constraints if input constraints */
+    for (i = 0; i < nb_operands; i++) {
+        op = &operands[i];
+        str = op->constraint;
+        str = skip_constraint_modifiers(str);
+        if (isnum(*str) || *str == '[') {
+            /* this is a reference to another constraint */
+            k = find_constraint(operands, nb_operands, str, NULL);
+            if ((unsigned) k >= i || i < nb_outputs)
+                tcc_error("invalid reference in constraint %d ('%s')",
+                          i, str);
+            op->ref_index = k;
+            if (operands[k].input_index >= 0)
+                tcc_error("cannot reference twice the same operand");
+            operands[k].input_index = i;
+            op->priority = 5;
+        } else if ((op->vt->r & VT_VALMASK) == VT_LOCAL
+                   && op->vt->sym
+                   && (reg = op->vt->sym->r & VT_VALMASK) < VT_CONST) {
+            op->priority = 1;
+            op->reg = reg;
+        } else {
+            op->priority = constraint_priority(str);
+        }
+    }
+
+    /* sort operands according to their priority */
+    for (i = 0; i < nb_operands; i++)
+        sorted_op[i] = i;
+    for (i = 0; i < nb_operands - 1; i++) {
+        for (j = i + 1; j < nb_operands; j++) {
+            p1 = operands[sorted_op[i]].priority;
+            p2 = operands[sorted_op[j]].priority;
+            if (p2 < p1) {
+                tmp = sorted_op[i];
+                sorted_op[i] = sorted_op[j];
+                sorted_op[j] = tmp;
+            }
+        }
+    }
+
+    for (i = 0; i < NB_ASM_REGS; i++) {
+        if (clobber_regs[i])
+            regs_allocated[i] = REG_IN_MASK | REG_OUT_MASK;
+        else
+            regs_allocated[i] = 0;
+    }
+    /* sp cannot be used */
+    regs_allocated[13] = REG_IN_MASK | REG_OUT_MASK;
+    /* fp cannot be used yet */
+    regs_allocated[11] = REG_IN_MASK | REG_OUT_MASK;
+
+    /* allocate registers and generate corresponding asm moves */
+    for (i = 0; i < nb_operands; i++) {
+        j = sorted_op[i];
+        op = &operands[j];
+        str = op->constraint;
+        /* no need to allocate references */
+        if (op->ref_index >= 0)
+            continue;
+        /* select if register is used for output, input or both */
+        if (op->input_index >= 0) {
+            reg_mask = REG_IN_MASK | REG_OUT_MASK;
+        } else if (j < nb_outputs) {
+            reg_mask = REG_OUT_MASK;
+        } else {
+            reg_mask = REG_IN_MASK;
+        }
+        if (op->reg >= 0) {
+            if (is_reg_allocated(op->reg))
+                tcc_error
+                    ("asm regvar requests register that's taken already");
+            reg = op->reg;
+            goto reg_found;
+        }
+      try_next:
+        c = *str++;
+        switch (c) {
+        case '=': // Operand is written-to
+            goto try_next;
+        case '+': // Operand is both READ and written-to
+            op->is_rw = 1;
+            /* FALL THRU */
+        case '&': // Operand is clobbered before the instruction is done using the input operands
+            if (j >= nb_outputs)
+                tcc_error("'%c' modifier can only be applied to outputs",
+                          c);
+            reg_mask = REG_IN_MASK | REG_OUT_MASK;
+            goto try_next;
+        case 'l': // In non-thumb mode, alias for 'r'--otherwise r0-r7 [ARM]
+        case 'r': // general-purpose register
+        case 'p': // loadable/storable address
+            /* any general register */
+            for (reg = 0; reg <= 8; reg++) {
+                if (!is_reg_allocated(reg))
+                    goto reg_found;
+            }
+            goto try_next;
+          reg_found:
+            /* now we can reload in the register */
+            op->is_llong = 0;
+            op->reg = reg;
+            regs_allocated[reg] |= reg_mask;
+            break;
+        case 'I': // integer that is valid as an data processing instruction immediate (0...255, rotated by a multiple of two)
+        case 'J': // integer in the range -4095 to 4095 [ARM]
+        case 'K': // integer that satisfies constraint I when inverted (one's complement)
+        case 'L': // integer that satisfies constraint I when inverted (two's complement)
+        case 'i': // immediate integer operand, including symbolic constants
+            if (!((op->vt->r & (VT_VALMASK | VT_LVAL)) == VT_CONST))
+                goto try_next;
+            break;
+        case 'M': // integer in the range 0 to 32
+            if (!
+                ((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) ==
+                 VT_CONST))
+                goto try_next;
+            break;
+        case 'm': // memory operand
+        case 'g':
+            /* nothing special to do because the operand is already in
+               memory, except if the pointer itself is stored in a
+               memory variable (VT_LLOCAL case) */
+            /* XXX: fix constant case */
+            /* if it is a reference to a memory zone, it must lie
+               in a register, so we reserve the register in the
+               input registers and a load will be generated
+               later */
+            if (j < nb_outputs || c == 'm') {
+                if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) {
+                    /* any general register */
+                    for (reg = 0; reg <= 8; reg++) {
+                        if (!(regs_allocated[reg] & REG_IN_MASK))
+                            goto reg_found1;
+                    }
+                    goto try_next;
+                  reg_found1:
+                    /* now we can reload in the register */
+                    regs_allocated[reg] |= REG_IN_MASK;
+                    op->reg = reg;
+                    op->is_memory = 1;
+                }
+            }
+            break;
+        default:
+            tcc_error("asm constraint %d ('%s') could not be satisfied",
+                      j, op->constraint);
+            break;
+        }
+        /* if a reference is present for that operand, we assign it too */
+        if (op->input_index >= 0) {
+            operands[op->input_index].reg = op->reg;
+            operands[op->input_index].is_llong = op->is_llong;
+        }
+    }
+
+    /* compute out_reg. It is used to store outputs registers to memory
+       locations references by pointers (VT_LLOCAL case) */
+    *pout_reg = -1;
+    for (i = 0; i < nb_operands; i++) {
+        op = &operands[i];
+        if (op->reg >= 0 &&
+            (op->vt->r & VT_VALMASK) == VT_LLOCAL && !op->is_memory) {
+            for (reg = 0; reg <= 8; reg++) {
+                if (!(regs_allocated[reg] & REG_OUT_MASK))
+                    goto reg_found2;
+            }
+            tcc_error("could not find free output register for reloading");
+          reg_found2:
+            *pout_reg = reg;
+            break;
+        }
+    }
+
+    /* print sorted constraints */
+#ifdef ASM_DEBUG
+    for (i = 0; i < nb_operands; i++) {
+        j = sorted_op[i];
+        op = &operands[j];
+        printf("%%%d [%s]: \"%s\" r=0x%04x reg=%d\n",
+               j,
+               op->id ? get_tok_str(op->id, NULL) : "",
+               op->constraint, op->vt->r, op->reg);
+    }
+    if (*pout_reg >= 0)
+        printf("out_reg=%d\n", *pout_reg);
+#endif
 }
 
 ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str)

_______________________________________________
Tinycc-devel mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel
Reply | Threaded
Open this post in threaded view
|

[PATCH 3/3] arm-asm: Implement subst_asm_operand

Danny Milosavljevic
In reply to this post by Danny Milosavljevic
---
 arm-asm.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 72 insertions(+), 1 deletion(-)

diff --git a/arm-asm.c b/arm-asm.c
index 881a6b9..3ade475 100644
--- a/arm-asm.c
+++ b/arm-asm.c
@@ -1209,7 +1209,78 @@ ST_FUNC void asm_opcode(TCCState *s1, int token)
 
 ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier)
 {
-    tcc_error("internal error: subst_asm_operand not implemented");
+    int r, reg, size, val;
+    char buf[64];
+
+    r = sv->r;
+    if ((r & VT_VALMASK) == VT_CONST) {
+        if (!(r & VT_LVAL) && modifier != 'c' && modifier != 'n' &&
+            modifier != 'P')
+            cstr_ccat(add_str, '#');
+        if (r & VT_SYM) {
+            const char *name = get_tok_str(sv->sym->v, NULL);
+            if (sv->sym->v >= SYM_FIRST_ANOM) {
+                /* In case of anonymous symbols ("L.42", used
+                   for static data labels) we can't find them
+                   in the C symbol table when later looking up
+                   this name.  So enter them now into the asm label
+                   list when we still know the symbol.  */
+                get_asm_sym(tok_alloc(name, strlen(name))->tok, sv->sym);
+            }
+            if (tcc_state->leading_underscore)
+                cstr_ccat(add_str, '_');
+            cstr_cat(add_str, name, -1);
+            if ((uint32_t) sv->c.i == 0)
+                goto no_offset;
+            cstr_ccat(add_str, '+');
+        }
+        val = sv->c.i;
+        if (modifier == 'n')
+            val = -val;
+        snprintf(buf, sizeof(buf), "%d", (int) sv->c.i);
+        cstr_cat(add_str, buf, -1);
+      no_offset:;
+    } else if ((r & VT_VALMASK) == VT_LOCAL) {
+        snprintf(buf, sizeof(buf), "[fp,#%d]", (int) sv->c.i);
+        cstr_cat(add_str, buf, -1);
+    } else if (r & VT_LVAL) {
+        reg = r & VT_VALMASK;
+        if (reg >= VT_CONST)
+            tcc_internal_error("");
+        snprintf(buf, sizeof(buf), "[%s]",
+                 get_tok_str(TOK_ASM_r0 + reg, NULL));
+        cstr_cat(add_str, buf, -1);
+    } else {
+        /* register case */
+        reg = r & VT_VALMASK;
+        if (reg >= VT_CONST)
+            tcc_internal_error("");
+
+        /* choose register operand size */
+        if ((sv->type.t & VT_BTYPE) == VT_BYTE ||
+            (sv->type.t & VT_BTYPE) == VT_BOOL)
+            size = 1;
+        else if ((sv->type.t & VT_BTYPE) == VT_SHORT)
+            size = 2;
+        else
+            size = 4;
+
+        if (modifier == 'b') {
+            size = 1;
+        } else if (modifier == 'w') {
+            size = 2;
+        } else if (modifier == 'k') {
+            size = 4;
+        }
+
+        switch (size) {
+        default:
+            reg = TOK_ASM_r0 + reg;
+            break;
+        }
+        snprintf(buf, sizeof(buf), "%s", get_tok_str(reg, NULL));
+        cstr_cat(add_str, buf, -1);
+    }
 }
 
 /* generate prolog and epilog code for asm statement */

_______________________________________________
Tinycc-devel mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel