Выполнение сборки, созданной Numba - PullRequest
7 голосов
/ 08 мая 2020

При странном повороте событий я оказался в следующей затруднительной ситуации, когда использую следующий код Python для записи сборки, созданной Numba, в файл:

@jit(nopython=True, nogil=True)
def six():
    return 6

with open("six.asm", "w") as f:
    for k, v in six.inspect_asm().items():
        f.write(v)

Код сборки успешно записан в файл, но я не могу понять, как его выполнить. Я пробовал следующее:

$ as -o six.o six.asm
$ ld six.o -o six.bin
$ chmod +x six.bin
$ ./six.bin

Однако этап связывания не удается со следующим:

ld: warning: cannot find entry symbol _start; defaulting to 00000000004000f0
six.o: In function `cpython::__main__::six$241':
<string>:(.text+0x20): undefined reference to `PyArg_UnpackTuple'
<string>:(.text+0x47): undefined reference to `PyEval_SaveThread'
<string>:(.text+0x53): undefined reference to `PyEval_RestoreThread'
<string>:(.text+0x62): undefined reference to `PyLong_FromLongLong'
<string>:(.text+0x74): undefined reference to `PyExc_RuntimeError'
<string>:(.text+0x88): undefined reference to `PyErr_SetString'

Я подозреваю, что стандарт Numba и / или Python библиотека должна быть динамически связана со сгенерированным объектным файлом, чтобы это работало успешно, но я не уверен, как это можно сделать (если это вообще возможно).

Я также попробовал следующее, где я пишу промежуточный код LLVM в файл вместо сборки:

with open("six.ll", "w") as f:
    for k, v in six.inspect_llvm().items():
        f.write(v)

А затем

$ lli six.ll

Но это тоже не удается со следующей ошибкой:

'main' function not found in module.

ОБНОВЛЕНИЕ:

Оказывается, существует утилита для поиска соответствующих флагов, передаваемых команде ld для динамического связывания стандарта Python. библиотека.

$ python3-config --ldflags

Возвращает

-L/Users/rayan/anaconda3/lib/python3.7/config-3.7m-darwin -lpython3.7m -ldl -framework CoreFoundation 

Повторное выполнение следующего, на этот раз с правильными флагами:

$ as -o six.o six.asm
$ ld six.o -o six.bin -L/Users/rayan/anaconda3/lib/python3.7/config-3.7m-darwin -lpython3.7m -ldl -framework CoreFoundation 
$ chmod +x six.bin
$ ./six.bin

Теперь я получаю

ld: warning: No version-min specified on command line
ld: entry point (_main) undefined. for inferred architecture x86_64

Я попытался добавить метку _main в файл сборки, но это d Похоже, он ничего не делает. Есть идеи о том, как определить точку входа?

ОБНОВЛЕНИЕ 2:

Вот код сборки на случай, если это полезно, похоже, что целевая функция та, с которой этикетка _ZN8__main__7six$241E:

    .text
    .file   "<string>"
    .globl  _ZN8__main__7six$241E
    .p2align    4, 0x90
    .type   _ZN8__main__7six$241E,@function
_ZN8__main__7six$241E:
    movq    $6, (%rdi)
    xorl    %eax, %eax
    retq
.Lfunc_end0:
    .size   _ZN8__main__7six$241E, .Lfunc_end0-_ZN8__main__7six$241E

    .globl  _ZN7cpython8__main__7six$241E
    .p2align    4, 0x90
    .type   _ZN7cpython8__main__7six$241E,@function
_ZN7cpython8__main__7six$241E:
    .cfi_startproc
    pushq   %rax
    .cfi_def_cfa_offset 16
    movq    %rsi, %rdi
    movabsq $.const.six, %rsi
    movabsq $PyArg_UnpackTuple, %r8
    xorl    %edx, %edx
    xorl    %ecx, %ecx
    xorl    %eax, %eax
    callq   *%r8
    testl   %eax, %eax
    je  .LBB1_3
    movabsq $_ZN08NumbaEnv8__main__7six$241E, %rax
    cmpq    $0, (%rax)
    je  .LBB1_2
    movabsq $PyEval_SaveThread, %rax
    callq   *%rax
    movabsq $PyEval_RestoreThread, %rcx
    movq    %rax, %rdi
    callq   *%rcx
    movabsq $PyLong_FromLongLong, %rax
    movl    $6, %edi
    popq    %rcx
    .cfi_def_cfa_offset 8
    jmpq    *%rax
.LBB1_2:
    .cfi_def_cfa_offset 16
    movabsq $PyExc_RuntimeError, %rdi
    movabsq $".const.missing Environment", %rsi
    movabsq $PyErr_SetString, %rax
    callq   *%rax
.LBB1_3:
    xorl    %eax, %eax
    popq    %rcx
    .cfi_def_cfa_offset 8
    retq
.Lfunc_end1:
    .size   _ZN7cpython8__main__7six$241E, .Lfunc_end1-_ZN7cpython8__main__7six$241E
    .cfi_endproc

    .globl  cfunc._ZN8__main__7six$241E
    .p2align    4, 0x90
    .type   cfunc._ZN8__main__7six$241E,@function
cfunc._ZN8__main__7six$241E:
    movl    $6, %eax
    retq
.Lfunc_end2:
    .size   cfunc._ZN8__main__7six$241E, .Lfunc_end2-cfunc._ZN8__main__7six$241E

    .type   _ZN08NumbaEnv8__main__7six$241E,@object
    .comm   _ZN08NumbaEnv8__main__7six$241E,8,8
    .type   .const.six,@object
    .section    .rodata,"a",@progbits
.const.six:
    .asciz  "six"
    .size   .const.six, 4

    .type   ".const.missing Environment",@object
    .p2align    4
.const.missing Environment:
    .asciz  "missing Environment"
    .size   ".const.missing Environment", 20


    .section    ".note.GNU-stack","",@progbits

1 Ответ

7 голосов
/ 18 мая 2020

После просмотра [PyData.Numba]: Numba do c s, и некоторой отладки, проб и ошибок, я пришел к выводу: похоже, вы отключились путь к вашему квесту (как также было указано в комментариях).

Numba преобразует Python код (функции) в машинный код (по очевидной причине : скорость). Он делает все (преобразование, сборку, вставку в текущий процесс) на лету, программисту нужно только украсить функцию как , например, @numba.jit ( [PyData.Numba]: Just-in -Время компиляции ).

Поведение, которое вы испытываете , является правильным . Объект Dispatcher (используется для украшения функции six ) только генерирует (ассемблерный) код для самой функции (там нет main , поскольку код выполняется в текущем процессе (Python интерпретатор основная функция)). Таким образом, компоновщик может пожаловаться на отсутствие символа main . Это похоже на запись файла C, который содержит только:

int six() {
    return 6;
}

Для того, чтобы все работало правильно, вам необходимо:

  1. Создать .asm файл в .o (объект) файл (готово)
  2. Включить файл .o из # 1. в библиотеку, которая может быть

    • Stati c
    • Dynami c


    Библиотека для связывания в (окончательном) исполняемом файле. Этот шаг не является обязательным, так как вы можете использовать файл .o напрямую

  3. Создайте другой файл, который определяет main (и вызывает six - что я предполагаю, что это вся цель) в файл .o . Поскольку мне не очень нравится сборка, я написал это в C
  4. Свяжите 2 объекта (из # 2. ( # 1 . ) и # 3. ) вместе

В качестве альтернативы вы можете взглянуть на [PyData.Numba]: Предварительная компиляция кода. , но имейте в виду, что при этом будет создан модуль Python (расширение).

Вернемся к текущей проблеме. Тест проводился на Ubuntu 18.04 64bit .

code00.py :

#!/usr/bin/env python

import sys
import numba


@numba.jit(nopython=True, nogil=True)
def six():
    return 6


def main(*argv):
    six()  # Call the function(s), otherwise `inspect_asm()` would return empty dict
    speed_funcs = [
        (six, numba.int32()),
    ]
    for func, _ in speed_funcs:
        file_name_asm = "{0:s}_numba_{1:s}.asm".format(sys.platform, func.__name__)
        asm = func.inspect_asm()
        print("Writing to {0:s}:".format(file_name_asm))
        with open(file_name_asm, "wb") as fout:
            for k, v in asm.items():
                print("    {0:}".format(k))
                fout.write(v.encode())


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main(*sys.argv[1:])
    print("\nDone.")

main00. c :

#include <stdio.h>
#include <dlfcn.h>

//#define SYMBOL_SIX "_ZN8__main__7six$241E"
#define SYMBOL_SIX "cfunc._ZN8__main__7six$241E"

typedef int (*SixFuncPtr)();

int main() {
    void *pMod = dlopen("./liblinux_numba.so", RTLD_LAZY);
    if (!pMod) {
        printf("Error (%s) loading module\n", dlerror());
        return -1;
    }
    SixFuncPtr pSixFunc = dlsym(pMod, SYMBOL_SIX);
    if (!pSixFunc)
    {
        printf("Error (%s) loading function\n", dlerror());
        dlclose(pMod);
        return -2;
    }
    printf("six() returned: %d\n", (*pSixFunc)());
    dlclose(pMod);
    return 0;
}

сборка. sh:

CC=gcc

FLAG_LD_LIB_LINUXNUMBA="-Wl,-L. -Wl,-llinux_numba"
FLAG_LD_LIB_PYTHON="-Wl,-L/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu -Wl,-lpython3.7m"

rm -f *.asm *.a *.o *.so *.exe

echo Generate .asm
python3 code00.py

echo Assemble
as -o linux_numba_six.o linux_numba_six.asm

echo Link library
LIB_NUMBA="./liblinux_numba.so"
#ar -scr ${LIB_NUMBA} linux_numba_six.o
${CC} -o ${LIB_NUMBA} -shared linux_numba_six.o ${FLAG_LD_LIB_PYTHON}

echo Dump library contents
nm -S ${LIB_NUMBA}
#objdump -t ${LIB_NUMBA}

echo Compile and link executable
${CC} -o main00.exe main00.c -ldl

echo Exit script

Вывод :

(py_venv_pc064_03.07.05_test0) [cfati@cfati-ubtu-18-064-00:~/Work/Dev/StackOverflow/q061678226]> ~/sopr.sh
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[064bit prompt]>
[064bit prompt]> ls
build.sh  code00.py  main00.c
[064bit prompt]>
[064bit prompt]> ./build.sh
Generate .asm
Python 3.7.5 (default, Nov  7 2019, 10:50:52) [GCC 8.3.0] 64bit on linux

Writing to linux_numba_six.asm:
    ()

Done.
Assemble
Link library
Dump library contents
0000000000201020 B __bss_start
00000000000008b0 0000000000000006 T cfunc._ZN8__main__7six$241E
0000000000201020 0000000000000001 b completed.7698
00000000000008e0 0000000000000014 r .const.missing Environment
00000000000008d0 0000000000000004 r .const.six
                 w __cxa_finalize
0000000000000730 t deregister_tm_clones
00000000000007c0 t __do_global_dtors_aux
0000000000200e58 t __do_global_dtors_aux_fini_array_entry
0000000000201018 d __dso_handle
0000000000200e60 d _DYNAMIC
0000000000201020 D _edata
0000000000201030 B _end
00000000000008b8 T _fini
0000000000000800 t frame_dummy
0000000000200e50 t __frame_dummy_init_array_entry
0000000000000990 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000000008f4 r __GNU_EH_FRAME_HDR
00000000000006f0 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U PyArg_UnpackTuple
                 U PyErr_SetString
                 U PyEval_RestoreThread
                 U PyEval_SaveThread
                 U PyExc_RuntimeError
                 U PyLong_FromLongLong
0000000000000770 t register_tm_clones
0000000000201020 d __TMC_END__
0000000000201028 0000000000000008 B _ZN08NumbaEnv8__main__7six$241E
0000000000000820 0000000000000086 T _ZN7cpython8__main__7six$241E
0000000000000810 0000000000000009 T _ZN8__main__7six$241E
Compile and link executable
Exit script
[064bit prompt]>
[064bit prompt]> ls
build.sh  code00.py  liblinux_numba.so  linux_numba_six.asm  linux_numba_six.o  main00.c  main00.exe
[064bit prompt]>
[064bit prompt]> # Run the executable
[064bit prompt]>
[064bit prompt]> ./main00.exe
six() returned: 6
[064bit prompt]>

Также отправка (так как это важно) linux_numba_six.asm :

    .text
    .file   "<string>"
    .globl  _ZN8__main__7six$241E
    .p2align    4, 0x90
    .type   _ZN8__main__7six$241E,@function
_ZN8__main__7six$241E:
    movl    $6, (%rdi)
    xorl    %eax, %eax
    retq
.Lfunc_end0:
    .size   _ZN8__main__7six$241E, .Lfunc_end0-_ZN8__main__7six$241E

    .globl  _ZN7cpython8__main__7six$241E
    .p2align    4, 0x90
    .type   _ZN7cpython8__main__7six$241E,@function
_ZN7cpython8__main__7six$241E:
    .cfi_startproc
    pushq   %rax
    .cfi_def_cfa_offset 16
    movq    %rsi, %rdi
    movabsq $.const.six, %rsi
    movabsq $PyArg_UnpackTuple, %r8
    xorl    %edx, %edx
    xorl    %ecx, %ecx
    xorl    %eax, %eax
    callq   *%r8
    testl   %eax, %eax
    je  .LBB1_3
    movabsq $_ZN08NumbaEnv8__main__7six$241E, %rax
    cmpq    $0, (%rax)
    je  .LBB1_2
    movabsq $PyEval_SaveThread, %rax
    callq   *%rax
    movabsq $PyEval_RestoreThread, %rcx
    movq    %rax, %rdi
    callq   *%rcx
    movabsq $PyLong_FromLongLong, %rax
    movl    $6, %edi
    popq    %rcx
    .cfi_def_cfa_offset 8
    jmpq    *%rax
.LBB1_2:
    .cfi_def_cfa_offset 16
    movabsq $PyExc_RuntimeError, %rdi
    movabsq $".const.missing Environment", %rsi
    movabsq $PyErr_SetString, %rax
    callq   *%rax
.LBB1_3:
    xorl    %eax, %eax
    popq    %rcx
    .cfi_def_cfa_offset 8
    retq
.Lfunc_end1:
    .size   _ZN7cpython8__main__7six$241E, .Lfunc_end1-_ZN7cpython8__main__7six$241E
    .cfi_endproc

    .globl  cfunc._ZN8__main__7six$241E
    .p2align    4, 0x90
    .type   cfunc._ZN8__main__7six$241E,@function
cfunc._ZN8__main__7six$241E:
    movl    $6, %eax
    retq
.Lfunc_end2:
    .size   cfunc._ZN8__main__7six$241E, .Lfunc_end2-cfunc._ZN8__main__7six$241E

    .type   _ZN08NumbaEnv8__main__7six$241E,@object
    .comm   _ZN08NumbaEnv8__main__7six$241E,8,8
    .type   .const.six,@object
    .section    .rodata,"a",@progbits
.const.six:
    .asciz  "six"
    .size   .const.six, 4

    .type   ".const.missing Environment",@object
    .p2align    4
".const.missing Environment":
    .asciz  "missing Environment"
    .size   ".const.missing Environment", 20


    .section    ".note.GNU-stack","",@progbits

Примечания :

  • linux_numba_six.asm (и все, что от него происходит) содержит код для функции six . На самом деле существует множество символов (на OSX , вы также можете использовать собственный otool -T), например:

    1. cfun c ._ ZN8__main__7six $ 241 E - сама функция (C)
    2. _ZN7cpython8__main__7six $ 241E - обертка Python:
    3. Выполняет преобразования C <=> Python (через Python API функции, такие как PyArg_UnpackTuple )
    4. Из-за # 1. ему требуется (зависит от) lib python3 .7m
    5. Как следствие, nopython=True не действует в этом случае
  • Из-за того, что простая функция C содержит точка (. ) в имени, я не мог вызвать его напрямую из C (так как это недопустимое имя идентификатора), поэтому мне пришлось загрузить ( .so и) функцию вручную ( dlopen / dlsym ), в результате получается больше кода, чем просто вызов функции.
    Я не пробовал, но думаю, что следующие (ручные) изменения в сгенерированном файле .asm упростят работу:
    • Переименование простого C имя функции (что-то вроде __ six или любого другого действительного C идентификатора, который также не имеет sh с другим (явным или внутреннее) имя) в файле .asm перед его сборкой, функция может быть напрямую вызвана из C
    • Удаление Python wrapper ( # 2. ) также избавится от # 22.



Обновление # 0

Спасибо @PeterCordes, который поделился этой точной информацией ( [GNU.GCC]: управляющие имена, используемые в коде ассемблера ), которого мне не хватало, вот гораздо более простая версия.

main01. c:

#include <stdio.h>

extern int six() asm ("cfunc._ZN8__main__7six$241E");

int main() {
    printf("six() returned: %d\n", six());
}

Вывод :

[064bit prompt]> # Resume from previous point + main01.c
[064bit prompt]>
[064bit prompt]> ls
build.sh  code00.py  liblinux_numba.so  linux_numba_six.asm  linux_numba_six.o  main00.c  main00.exe  main01.c
[064bit prompt]>
[064bit prompt]> ar -scr liblinux_numba.a linux_numba_six.o
[064bit prompt]>
[064bit prompt]> gcc -o main01.exe main01.c ./liblinux_numba.a -Wl,-L/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu -Wl,-lpython3.7m
[064bit prompt]>
[064bit prompt]> ls
build.sh  code00.py  liblinux_numba.a  liblinux_numba.so  linux_numba_six.asm  linux_numba_six.o  main00.c  main00.exe  main01.c  main01.exe
[064bit prompt]>
[064bit prompt]> ./main01.exe
six() returned: 6
[064bit prompt]>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...