Extensão PHP e Kotlin Native. Parte três, provavelmente final

Na primeira parte , são contadas coisas bastante básicas sobre a configuração de ferramentas e conceitos gerais.

A segunda parte é sobre, por assim dizer, a primeira abordagem ao projétil, idéias, planos, planos.

Neste artigo, haverá um pouco mais de explicações sobre o interope C e K / N, muitas macros, dor, desesperança e "raios de bondade". É claro que haverá um capítulo com uma história sobre conquistas (você não se elogiará ... e, como bônus, uma história sobre um fakap épico.

Isenção de responsabilidade: todos os itens a seguir são considerados no contexto da criação de uma biblioteca para PHP.

Capítulo Um Interope ingênuo


Como usar as funções K / N em C é descrito na primeira parte do ciclo. Portanto, aqui vou lhe dizer como usar as funções C em K / N.

A documentação oficial é bastante mesquinha e concisa, no entanto, para projetos simples, é suficiente.

Em resumo, você precisa criar um arquivo especial com a extensão .def e especificar os arquivos de cabeçalho necessários.

headers = php.h 

Em seguida, alimente-o com um programa chamado cinterop .

 # cinterop -def php.def -o php 

Na saída, você obterá a biblioteca libphp.klib que contém o código de bit llvm e várias meta-informações.

Em seguida, você pode usar com segurança as funções e macros descritas no arquivo de cabeçalho ( #define ), sem esquecer de conectar a biblioteca no estágio de compilação.

 # kotlinc -opt -produce static ${SOURCES} -l libphp.klib -o myLib 

Mas há uma nuance. E não um.

Na forma descrita acima, a biblioteca não será montada


Porque Mas porque as seguintes linhas estão presentes no php.h:

 #include "php_version.h" #include "zend.h" #include "zend_sort.h" #include "php_compat.h" #include "zend_API.h" 

Aqui deve-se notar que o llvm ainda está envolvido na compilação da biblioteca e possui a opção -I e o cinterop tem a opção -copt . Bem, você entendeu. Como resultado, esse comando é suficiente para compilar o php.h.

 # cinterop -def my.def -o myLib -I${PHP_LIB_ROOT} -copt -I${PHP_LIB_ROOT} \ -copt -I${PHP_LIB_ROOT}/main \ -copt -I${PHP_LIB_ROOT}/Zend \ -copt -I${PHP_LIB_ROOT}/TSRM 

Macros. Eu te amo e odeio! Não, eu odeio isso.


Tudo que você precisa saber sobre #define em termos de inter> C / K / N é
Toda macro C que se expande para uma constante é representada como propriedade Kotlin. Outras macros não são suportadas.

E então lembramos que a extensão PHP é uma macro em uma macro e aciona uma macro e tenta não chorar.

Mas nem tudo é tão ruim. Para contornar essa situação, os desenvolvedores de K / N forneceram um rolo de fita elétrica azul para anexar ao arquivo def de declarações personalizadas . Parece com isso (por exemplo, pegue a macro Z_TYPE_P )

 headers = php.h --- static inline zend_uchar __zp_get_arg_type(zval *z_value) { return Z_TYPE_P(z_value); } 

Agora, no código K / N, será possível usar a função __zp_get_arg_type

Capítulo Dois Configurações INI do PHP ou uma macro sub-sub.


Este é um "raio do bem" para o código fonte do PHP.

Existem 4 macros para extrair configurações:

 INI_INT(val) INI_FLT(val) INI_STR(val) INI_BOOL(val) 

Onde val é uma sequência com o nome da configuração.

Agora, vejamos o exemplo de INI_STR para ver como essa macro é definida.

 #define INI_STR(name) zend_ini_string_ex((name), sizeof(name)-1, 0, NULL) 

Já reparou na sua "falha fatal"?

Caso contrário, deixe-me dizer: esse é o sizeof função. Quando você usa a macro diretamente, tudo está bem:

 php_printf("The value is : %s", INI_STR("my.ini")); 

Quando você o utiliza por meio de uma função proxy de um arquivo .def , o carro se transforma em abóbora e sizeof (nome) retorna o tamanho do ponteiro. Xeque-mate Kotlin Native.

De fato, existem apenas duas opções para contornar.

  1. Não use macros, mas funções às quais estão anexadas.
  2. O invólucro de código rígido funciona para cada configuração necessária.

A primeira opção é melhor para todos que a segunda, exceto por um ponto - ninguém garante que a macro declaração não será alterada. Portanto, para o meu projeto, eu, com um sentimento de profunda insatisfação, escolhi a segunda opção.

CAPÍTULO TRÊS Depurar? Que debag?


Ato 1 - Interoperabilidade.


Em um ponto, depois de anexar a fita azul ao arquivo def de 20 funções de proxy consecutivas, recebi um erro maravilhoso.

 Exception in thread "main" java.lang.Error: /tmp/tmp399964332777824085.c:103:38: error: too many arguments to function call, expected 2, have 3 at org.jetbrains.kotlin.native.interop.indexer.UtilsKt.ensureNoCompileErrors(Utils.kt:137) at org.jetbrains.kotlin.native.interop.indexer.IndexerKt.indexDeclarations(Indexer.kt:902) at org.jetbrains.kotlin.native.interop.indexer.IndexerKt.buildNativeIndexImpl(Indexer.kt:892) at org.jetbrains.kotlin.native.interop.indexer.NativeIndexKt.buildNativeIndex(NativeIndex.kt:56) at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.processCLib(main.kt:283) at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.interop(main.kt:38) at org.jetbrains.kotlin.cli.utilities.InteropCompilerKt.invokeInterop(InteropCompiler.kt:100) at org.jetbrains.kotlin.cli.utilities.MainKt.main(main.kt:29) 


Comente sobre metade, recompile, se comentar sobre a metade do resto for repetido, coletamos ... E dado que o processo de compilação dos cabeçalhos é longo o suficiente ... (sim, parecia mais rápido do que subir uma dúzia de arquivos de origem e meticulosamente, com uma lupa, reconciliar).

O segundo "raio do bem" vai para o JetBrains.


Ato 2 - tempo de execução.


Eu recebo uma falha de segmentação em tempo de execução. Bem, isso acontece. Estou subindo no depurador. Ummm ... STA?

 Program received signal SIGSEGV, Segmentation fault. kfun:kotlinx.cinterop.toKString@kotlinx.cinterop.CPointer<kotlinx.cinterop.ByteVarOf<kotlin.Byte>>.()kotlin.String () at /opt/buildAgent/work/4d622a065c544371/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt:402 402 /opt/buildAgent/work/4d622a065c544371/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt: No such file or directory. 


Capítulo Quatro Coloquei chá no seu chá para que você possa tomar chá enquanto bebe.


Aqui é necessário contar como funciona essa porcaria que eu faço.

Você escreve uma DSL descrevendo a futura extensão PHP, escreve código K / N com a implementação de funções, classes e métodos, executa o make e, milagrosamente, obtém uma biblioteca pronta que pode ser conectada ao PHP.

A montagem pode ser dividida em 4 etapas:

  1. Criando uma camada entre C e K / N (a mesma cinterop )
  2. Geração de código da extensão C
  3. Compilando uma Biblioteca com Lógica
  4. Compilando a biblioteca de destino

O objetivo é adicionar a capacidade de criar instâncias de uma classe PHP no código K / N. Por exemplo, para que a classe possa definir o método getInstance() . E eu quero fazer com que seja conveniente de usar.

Em C, esse problema é resolvido uma ou duas vezes.

 zval *obj = malloc(sizeof(zval)); object_init_ex(obj, myClass); 

Parece simples - pegue-o e transfira-o para K / N, mas aqui está o myClass ...

Mas myClass é uma variável global do tipo zend_class_entry* , declarada no código C do projeto e com um nome desconhecido com antecedência.

Assista suas mãos. Você precisa compilar a biblioteca a partir do código K / N, no qual haverá uma função que precisa ter acesso ao myClass , definido no código C gerado, mas não compilado, a partir do qual essa função será chamada.

Por fim, a implementação dessa funcionalidade levou à adição de dois novos artefatos: .h e .kt no estágio de geração de código, complicação no estágio cinterop e no épico phakap, sobre o qual falarei no final.

Capítulo Cinco O que está em meu nome?


O Conto do Porquê:

 enum class ArgumentType { PHP_STRING, PHP_LONG, PHP_DOUBLE, PHP_NULL, ... } 

melhor que:

 enum class ArgumentType { STRING, LONG, DOUBLE, NULL, ... } 

Sim, nem é necessário explicar. É isso que ArgumentType.NULL se transforma no arquivo de cabeçalho da biblioteca Kotlin:

 struct { extension_kt_kref_php_extension_dsl_ArgumentType (*get)(); /* enum entry for NULL. */ } NULL; 

E é assim que o gcc reage a isso.

 /root/simpleExtension/phpmodule/extension_kt_api.h:113:17: error: expected identifier or '(' before 'void' } NULL; ^ 

A cortina! Cuidado com os nomes.

O penúltimo capítulo. Você não se louvará - ninguém louvará.


De modo geral, alcancei meus objetivos. Imerso no assunto, a "estrutura" para escrever extensões PHP no Kotlin Native, em geral, está pronta. Resta acrescentar algumas funcionalidades, e não as mais críticas, e polimento.

O projeto em si e, espero, uma boa documentação, podem ser vistos no github .

O que posso dizer sobre K / N? Apenas bom. Escrever sobre isso é um prazer, e pequenos cardumes e rugosidade podem ser atribuídos ao fato de que ele nem saiu do berço :)

O último capítulo. Raios de bom, sem aspas.


E agora, absolutamente sério e com profundo respeito, quero agradecer aos caras do JetBrains e aos residentes do canal folgado do Kotlin Native. Você é super!

E um agradecimento especial a Nicholas Igotti .



Bônus Epic Fakap.


O contexto é descrito no quarto capítulo.

Na verdade, quando tudo foi adicionado a um estado em que foi compilado sem erros, surgiu um problema - durante o teste, o PHP me abriu de um lado completamente desconhecido.

 # php -dextension=./phpmodule/modules/extension.so -r "var_dump(ExampleClass::getInstance());" *RECURSION* # 

"Figas!" - Pensei, entrei no código-fonte PHP e encontrei um pedaço desse tipo.

 case IS_OBJECT: if (Z_IS_RECURSIVE_P(struc)) { PUTS("*RECURSION*\n"); return; } 

Adicionando depuração:

 printf("%u", Z_IS_RECURSIVE_P(struc)) 

levou a:

 undefined symbol: Z_IS_RECURSIVE_P in Unknown on line 0 

"Figas!" Pensei de novo.

Naquele momento, quando imaginei examinar o php.h (7.1.8) realmente usado no host linux, e não aquele que foi retirado do github do brunch principal (7.3.x), um dia passou. Em linha reta envergonhado.

Mas, como se viu, não era uma bobina.

O código de verificação de recursão correto, em todas as fases da vida do objeto que eu controlei, relatou que tudo deveria funcionar. E isso significa que você deve olhar cuidadosamente para aqueles lugares que eu não controlo. Havia exatamente uma dessas coisas - na qual meu objeto é retornado à função var_dump

 RETURN_OBJ( example_symbols()->kotlin.root.php.extension.proxy.objectToZval( example_symbols()->kotlin.root.exampleclass.getInstance(/* */) ) ) 

Abra a macro RETURN_OBJ até o final. Tire os monitores nervosos e grávidos!

 1) RETURN_OBJ(r) 2) { RETVAL_OBJ(r); return; } 3) { ZVAL_OBJ(return_value, r); return; } 4) { do { zval *__z = (return_value); Z_OBJ_P(__z) = (r); Z_TYPE_INFO_P(__z) = IS_OBJECT_EX; } while (0); return; } 5) { do { zval *__z = (return_value); Z_OBJ(*(__z)) = (r); Z_TYPE_INFO(*(__z)) = (IS_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)); } while (0); return; } 6) { do { zval *__z = (return_value); (*(__z)).value.obj = (r); (*(__z)).u1.type_info = (8 | ((1<<0) << 8)); } while (0); return; } 

Aqui, então, senti vergonha pela segunda vez. Eu, completamente no meu olho azul, empurrei zval* para onde zend_object* esperando e passei quase dois dias procurando pelo erro.

Obrigado a todos Kotlin! :)

PS. Se existe uma alma bondosa que lê meu inglês desajeitado e corrige a documentação - não haverá limite para minha gratidão.

Source: https://habr.com/ru/post/pt423145/


All Articles