G CC режим инструкций руки при компиляции в режиме большого пальца - PullRequest
6 голосов
/ 10 марта 2020

Мне интересно, как G CC, настроенный с использованием --with-mode=thumb, обрабатывает компиляцию / сборку кода, который использует разделы режима ARM, если не указан флаг -marm. То есть:

  • G CC компилируется с --with-mode=thumb
  • Программа компилируется без -marm (по умолчанию в режиме большого пальца)
  • Сборка раздел этой программы использует режим ARM

Я попытался скомпилировать небольшую тестовую программу на Raspberry Pi 4 с ядром Ubuntu 18.04.4 5.3.0-1018-raspi2 и заметил, что раздел .arm выполняется в режиме инструкций 16-битного большого пальца, что побудило меня исследовать это. Это естественно вызывает ошибку сегментации, так как счетчик программы увеличивается на 2 байта вместо 4.

Вот что говорит GDB в режиме layout asm, когда моя программа разветвляется на код сборки .arm и после того, как я выполняю один stepi команда:

0x400900 <asm_maxfilter>        push   {r4, lr}
0x400904 <asm_maxfilter+4>      mov    r3, #0
0x400908 <filter_loop>          vld1.8 {d0-d1}, [r0]

pc 0x400902 0x400902 <asm_maxfilter+2>
^ The program counter is between instructions

Мой код выглядит следующим образом:

#include <arm_neon.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void asm_maxfilter(unsigned char* upbuffer, unsigned char* longterm_buffer, int grid_size);

int main(int argc, char** argv) {

    const int pixels_per = 16;
    const int grid_reso = 256;
    const int grid_size = grid_reso * grid_reso;
    const int remainder = grid_size % pixels_per;
    const int work_count = grid_size - remainder;

    unsigned char* longterm_up = (unsigned char*)malloc(grid_reso * grid_reso);
    memset(longterm_up, 0, grid_reso * grid_reso);

    unsigned char* up_buffers[60];
    int u;
    int i;

    for (u = 0; u < 60; ++u) {
        up_buffers[u] = (unsigned char*)malloc(grid_reso * grid_reso);

        if (up_buffers[u] == NULL) {
            fprintf(stderr, "Failed mallocing\n");
            return 1;
        }

        memset(up_buffers[u], 0, grid_reso * grid_reso);
    }

    for (u = 0; u < 60; ++u) {

        asm_maxfilter(up_buffers[u], longterm_up, work_count);

        // non-SIMD version handles the remainder that did not fit in NEON registers
        for (i = grid_size - remainder; i < grid_size; ++i) {
            if (longterm_up[i] < up_buffers[u][i]) {
                longterm_up[i] = up_buffers[u][i];
            }
        }
    }

    for (u = 0; u < 60; ++u) {
        free(up_buffers[u]);
    }

    free(longterm_up);

    return 0;
}

Сборка:

@ ARM NEON version of a max filter. Performs the following operation:
@
@ for (int i = 0; i < buf_size; ++i) {
@   if (buf_b[i] < buf_a[i]) {
@       buf_b[i] = buf_a[i];
@   }
@ }

.arm
.section .text
.align 4
.globl asm_maxfilter

@ parameters
@ r0: buf_a
@ r1: buf_b
@ r2: buf_size, multiple of 16
asm_maxfilter:

    @ Store register states in stack. They must be restored before returning
    push { r4, lr }

    @ Reset counter
    mov r3, #0

    filter_loop:

        @ Load 16 bytes into vectors
        vld1.u8 {q0}, [r0]
        vld1.u8 {q1}, [r1]

        @ Find greater values in each vector
        vcgt.u8 q2, q0, q1

        @ Bitselect the greater value into q2
        vbsl.u8 q2, q0, q1

        @ Store the larger value in output buffer
        vst1.u8 {q2}, [r1]

        @ Increment counter by 16
        add r3, r3, #16

        @ Increment pointers
        add r0, r0, #16
        add r1, r1, #16

        @ Check if loop is done
        cmp r3, r2
        blt filter_loop

    @ Restore registers to their original state
    pop { r4, lr }

    @ lr register contains return address
    bx lr

.end

Код скомпилирован с использованием:

gcc -Wall -Wpedantic -O0 -g -march=armv8-a -mfloat-abi=hard -mtune=cortex-a72 -mfpu=neon -c -o main.o main.c
gcc -Wall -Wpedantic -O0 -g -march=armv8-a -mfloat-abi=hard -mtune=cortex-a72 -mfpu=neon -o neon_test ./main.o ./asm_test.s

Исходя из того, что написано в документации ARM, если процессору нужно переключаться между большим пальцем / рукой, программа должна выполнить ветвление, используя инструкцию BLX или BX:

https://developer.arm.com/docs/100076/0100/instruction-set-overview/overview-of-aarch32-state/changing-between-a32-and-t32-instruction-set-states

Цитата:

To direct armasm to generate A32 or T32 instruction encodings, you must set the assembler mode using an ARM or THUMB directive. Assembly code using CODE32 and CODE16 directives can still be assembled, but Arm recommends you use the ARM and THUMB directives for new code.

These directives do not change the instruction set state of the processor. To do this, you must use an appropriate instruction, for example BX or BLX to change between A32 and T32 states when performing a branch.

После разборки моей программы я заметил, что переключение режимов не выполняется. Это то, что программист должен делать самостоятельно в коде ассемблера (даже если ветвление происходит из кода C), или компилятор / ассемблер должен это обрабатывать?

Я также пытался указать __attribute__((target("arm"))) в C объявление файловой функции, то есть:

__attribute__((target("arm")))
void asm_maxfilter(unsigned char* upbuffer, unsigned char* longterm_buffer, int grid_size);

Однако, похоже, это ничего не изменило. Все работает правильно, как только я компилирую с -marm или использую G CC, у которого нет --with-mode=thumb

Ответы [ 2 ]

4 голосов
/ 11 марта 2020

Как предложил old_timer в комментарии, проблема заключалась в том, что исходный код сборки не включал .type asm_maxfilter, %function перед меткой. Код рабочей сборки начинается следующим образом:

.arm
.section .text
.align 4
.globl asm_maxfilter

.type asm_maxfilter, %function
asm_maxfilter:

    @ Store register states in stack. They must be restored before returning
    push { r4, lr }

    @ Reset counter
    mov r3, #0
    ...

Если ситуация была обратной (программа режима ARM с использованием функции большого пальца), то вместо .type asm_maxfilter, %function тип должен был быть .thumb_func.

Согласно ответу Джестера, я заметил, что объектный файл кода C действительно имеет сегмент перемещения R_ARM_THM_CALL, но без использования макроса .type инструкция перехода не была заменена инструкцией bx.

Если реализовать функцию ARM в файле C, используя __attribute__((target("arm"))) без внешней сборки, ie:

#include <stdio.h>
#include <stdlib.h>

__attribute__((target("arm")))
void foo(int a) {
    int b = 6*a;
    fprintf(stderr, "%d\n", b*5);
}

int main(int argc, char** argv) {
    int asd = atoi(argv[1]);
    foo(asd);
    return 0;
}

Тогда можно будет соблюдать правильную инструкцию blx используется в сгенерированном двоичном файле. Проблема, с которой я столкнулся, касается только случая, когда используется отдельный код сборки в отдельном файле, который не проходит через компилятор.

3 голосов
/ 10 марта 2020

Компоновщик должен позаботиться об этом автоматически. Если вы objdump -dr объектный файл, вы должны увидеть bl с перемещением R_ARM_THM_CALL, например:

  10:   f7ff fffe   bl  0 <asm_maxfilter>
            10: R_ARM_THM_CALL  asm_maxfilter

Компоновщик увидит, что asm_maxfilter является функцией охраны, и повернет bl в blx, поэтому конечный исполняемый файл может выглядеть следующим образом:

8360:       f000 e808       blx     8374 <asm_maxfilter>
...