Lapack: Permitir uma possível instalação da biblioteca index-64 junto com a biblioteca index-32 padrão?

Criado em 1 nov. 2020  ·  41Comentários  ·  Fonte: Reference-LAPACK/lapack

Atualmente o debian tem (alguns) cabeçalhos separados para blas64 e cblas64 (embora não da implementação de referência).

Não tenho certeza se eles estão corretos ou não, no que diz respeito à API index64 de referência (eles são da biblioteca blis).

Seria possível adicionar uma opção para cmake, algo como BUILD_INDEX64 , que é padronizado como OFF , mas se estiver ativado, ele criará as bibliotecas index-64?
Se eu fizer um RP para essa opção, será considerado uma possibilidade?

Algumas coisas que eu tinha em mente para permitir que isso coexistisse com a instalação padrão - nomeie as bibliotecas como libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so , desta forma não há conflito entre os nomes das bibliotecas (embora, é claro, você não possa vincular com libblas e libblas64 ao mesmo tempo).
Além disso, a biblioteca precisaria ser compilada duas vezes, uma para index32 e outra para index64 (mas esse é um cenário perfeitamente normal e não um obstáculo).
O único conflito que não consigo pensar em uma boa resolução são os nomes dos arquivos de cabeçalho.
Se seguir o estilo debians, pode ser sábio chamar os cabeçalhos c terminando com 64 também.
(Estou mantendo isso para o Gentoo e gostaria de manter o ecossistema bem próximo ao debian, para que tenhamos problemas mínimos para os desenvolvedores trocarem de sistemas)

Estou aberto a qualquer sugestão antes de fazer o PR: coração:

Obrigado,
Aisha

Build System Enhancement

Todos 41 comentários

Olá, Aisha, isso faz todo o sentido para mim, mas deixe-nos ver se temos o feedback de outras pessoas. Então vamos esperar alguns dias. J

Definitivamente, parece um bom plano.

OMG @langou
você é tão rápido: coração:

Apenas para completar, estou escrevendo coisas que ainda temos que fazer:

  • Descubra como nomear cabeçalhos para que a API de 32 bits possa coexistir com a API de 64 bits
  • Corrija as instruções printf / fprintf para que usem o qualificador correto para impressão.

Quaisquer sugestões para resolver o primeiro ponto são bem-vindas, infelizmente não tenho uma solução "limpa".

Algumas perguntas que me ajudarão a lidar com a nomenclatura dos arquivos

  • Parece haver uma tonelada de definições duplicadas entre cblas_f77.h e cblas_test.h . Nós realmente precisamos disso?
  • cblas_test.h precisa ser instalado? Dado seu nome (e os arquivos em que é usado), presumo que seja usado apenas durante a fase de teste. Talvez não devêssemos instalar este arquivo em todo o sistema?

Olá @ epsilon-0,

Algumas coisas que eu tinha em mente para permitir que isso coexistisse com a instalação padrão - nomeie as bibliotecas para libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so, dessa forma não há conflito entre os nomes das bibliotecas ( embora, é claro, você não possa vincular a libblas e a libblas64 ao mesmo tempo).

você pode estar procurando por PR # 218. O autor deste PR é Björn Esser do Projeto Fedora.

Olá @ epsilon-0. # 462 resolveu este problema?

@weslleyspereira não,
É necessário renomear / manipular mais alguns cabeçalhos.
Estarei ocupado nas próximas semanas, então não poderei fazer isso em breve.
Esboço básico

  • os arquivos de cabeçalho devem ser chamados de cblas.h e cblas64.h , da mesma forma para outros cabeçalhos

    • isso significa que os arquivos *.c precisariam de alguns pequenos ajustes para incluir o cabeçalho adequado, mas isso é apenas durante o tempo de compilação, portanto, pode ser hackeado.

  • os arquivos cmake devem ser instalados em lapack64 ou cblas64 , etc.

OK eu vejo. Obrigado pelo acompanhamento rápido!

Tenho problemas semelhantes ao tentar empacotar coisas com o pkgsrc. Gostaria de ter uma instalação completa da referência, com cblas e lapacke. Para diferentes implementações sendo instaladas ao mesmo tempo, eu me conformei com diferentes nomes de biblioteca e subdiretórios para os cabeçalhos, então, por exemplo

/usr/lib/libopenblas.so
/usr/lib/libopenblas64.so
/usr/lib/libblas.so
/usr/lib/libcblas.so
/usr/lib/libblas64.so
/usr/lib/libcblas64.so
/usr/include/openblas/cblas.h
/usr/include/openblas64/cblas.h
/usr/include/netlib/cblas.h
/usr/include/netlib64/cblas.h
/usr/include/cblas.h -> netlib/cblas.h (for compatibility, having the default)

(e assim por diante)

Não consideramos a troca de tempo de execução como as distros binárias, então está tudo bem se cada cblas.h (e lapacke.h) for específico para sua biblioteca correspondente, como com nomes extras para libopenblas. A seleção de tempo de construção acontece via

BLAS_INCLUDES=-I/prefix/include/netlib64
BLAS_LIBS=-lblas64
CBLAS_LIBS=-lcblas64

(etc.) Isso é o que os arquivos .pc devem dizer, e é muito mais fácil do que comunicar um nome de arquivo de cabeçalho diferente. Eles ainda não são consistentes nisso, mas estou consertando. Parece que até agora as pessoas apenas hackearam isso em suas distros, se incomodando com todas as bibliotecas de referência.

Eu tenho uma pergunta sobre esses cabeçalhos, no entanto.

Estou hackeando a compilação do cmake para obter cada componente construído separadamente e estou tentando alguma outra correção (consulte https://github.com/Reference-LAPACK/lapack/pull/556). Eu pego as bibliotecas libblas.so e libblas64.so construídas bem, eu consigo os dirs de cabeçalho configurados ... mas o cblas.h e lapacke.h instalados são idênticos para as versões de indexação de 32 e 64 bits. Isso está em desacordo com o openblas: aí, eu tenho uma diferença crucial que não vejo nas compilações do netlib:

diff -ruN /data/pkg/include/openblas/openblas_config.h /data/pkg/include/openblas64/openblas_config.h
--- /data/pkg/include/openblas/openblas_config.h    2021-06-03 19:03:53.000000000 +0200
+++ /data/pkg/include/openblas64/openblas_config.h  2021-06-03 19:13:36.000000000 +0200
@@ -44,6 +44,7 @@
 #define OPENBLAS_DLOCAL_BUFFER_SIZE 32768
 #define OPENBLAS_CLOCAL_BUFFER_SIZE 16384
 #define OPENBLAS_ZLOCAL_BUFFER_SIZE 12288
+#define OPENBLAS_USE64BITINT 
 #define OPENBLAS_GEMM_MULTITHREAD_THRESHOLD 4
 #define OPENBLAS_VERSION " OpenBLAS 0.3.15 "
 /*This is only for "make install" target.*/

Para as bibliotecas de referência, todos os cabeçalhos das compilações de índice de 32 e 64 bits são idênticos e, aparentemente, os usuários devem colocar
-DWeirdNEC em suas bandeiras (pode ter sido engraçado 30 anos atrás) para cblas.he -DLAPACK_ILP64 -DHAVE_LAPACK_CONFIG_H . Uma vez que as pessoas usam as bibliotecas BLAS otimizadas em produção, o padrão de fato é não expor isso aos usuários. Estes retornam sobre a referência, IMHO e os cabeçalhos instalados a partir de uma compilação ILP64 não devem exigir sinalizadores funky para evitar travar seu aplicativo ao vincular à biblioteca de 64 bits.

Concordamos que é a solução certa modificar os cabeçalhos no momento da construção para definir os inteiros corretos?

A propósito, os arquivos de configuração cblas que são instalados também perdem qualquer referência aos defs necessários, portanto, são quebrados para as compilações de índice de 64 bits, como parece. Mas, na verdade, penso em não instalá-los de forma alguma. Eles são redundantes com os arquivos .pc e tornam possivelmente mais difícil convencer os pacotes dependentes que usam cmake a aceitar uma escolha do empacotador por meio de BLAS_LIBS etal.

PS: Com Intel MKL, há um switch central -DMKL_ILP64 a ser definido. Eu imagino uma configuração trivial
include/intel-mkl64/cblas.h com

#ifndef MKL_ILP64
#define MKL_ILP64
#endif
#include <mkl_cblas.h>

para se adequar ao esquema geral. Eu também poderia colocar o define em BLAS_INCLUDES, o mesmo para os define netlib estranhos. O que é melhor? Queremos fazer como Intel ou OpenBLAS?

Concordamos que é a solução certa modificar os cabeçalhos no momento da construção para definir os inteiros corretos?

sim. Concordo e prefiro a solução que não replique o cabeçalho inteiro. Eu acho que é mais limpo.

A propósito, os arquivos de configuração cblas que são instalados também perdem qualquer referência aos defs necessários, portanto, são quebrados para as compilações de índice de 64 bits, como parece.

Direito. Acabei de instalar as bibliotecas de 64 bits (BUILD_INDEX64 = ON) e não consegui ver nada me dizendo para usar WeirdNEC , LAPACK_ILP64 ou HAVE_LAPACK_CONFIG_H . Obrigado por notar isso!

sim. Concordo e prefiro a solução que não replique o cabeçalho inteiro. Eu acho que é mais limpo.

Isso é ambíguo para mim. Qual é a solução mais limpa? O que estou preparando agora é:

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

O CMakeFile deve substituir HAVE_ILP por 1 ou 0, o cabeçalho resultante sendo instalado para a construção atual.

(A propósito: long não funcionaria no Windows. É long long there ... ou int64_t em todas as plataformas com stdint.)

Direito. Acabei de instalar as bibliotecas de 64 bits (BUILD_INDEX64 = ON) e não consegui ver nada me dizendo para usar WeirdNEC , LAPACK_ILP64 ou HAVE_LAPACK_CONFIG_H . Obrigado por notar isso!

Estou imaginando um futuro onde você faz

cc -I/foo/include/netlib64 -o bar bar.c -L/foo/lib -lcblas64

E as coisas são tratadas em foo / include / netlib64 / cblas.h, caso contrário, por foo / include / netlib / cblas.h (possivelmente vinculado a foo / include / cblas.h).

Tenho a suspeita de que _não_ é isso que você quis dizer, mas quero convencer que é melhor ;-)

Você pode tentar não duplicar o cabeçalho colocando 'o' cabeçalho em /foo/include/cblas.he fazer com que /foo/include/netlib64/cblas.h inclua aquele apenas definindo WeirdNEC, mas isso significa que o 64 pacotes de bits e de 32 bits compartilham esse arquivo de cabeçalho comum, que é complicado para o empacotamento. É muito melhor se cada um colocar seu arquivo em lugares / nomes separados. O nome precisa permanecer cblas.h porque você não quer sair por aí substituindo #include <cblas.h> linhas.

Edit: Além disso, ter cblas.h include ../cblas.h é confuso por si só. Também definimos o diretório de instalação do cabeçalho _one_ para cmake. Por padrão, é / foo / include, não / foo / netlib64 / include. Não vou mudar esse padrão. Os empacotadores terão que especificar o subdiretório como este (BSD make no pkgsrc):

.if !empty(LAPACK_COMPONENT:M*64)
.  if empty(MACHINE_ARCH:M*64)
PKG_FAIL_REASON+=       "${LAPACK_COMPONENT} incompatible with non-64-bit platform"
.  endif
HEADERDIR=netlib64
.else
HEADERDIR=netlib
.endif

# Note: We patch the build to install both static and
# shared libraries.
CMAKE_ARGS=     -DBUILD_DEPRECATED=ON \
                -DBUILD_SHARED_LIBS=ON \
                -DBUILD_STATIC_LIBS=ON \
                -DCMAKE_INSTALL_INCLUDEDIR=${PREFIX}/include/${HEADERDIR} \
                ${LAPACK_COMPONENT_CMAKE_ARGS}

Um belo aspecto do envio / instalação do cblas.h de 32 bits com essa modificação para o local usual é que a mecânica original ainda funciona. Apenas a variante de 64 bits aplicará o WeirdNEC. Você pode decidir instalar apenas o de 64 bits em um prefixo e manter as outras partes do ecossistema intactas.

Oh, vamos lá… o CBLAS / cmake / cblas-config-install.cmake.in parece esquecer -DCMAKE_INSTALL_INCLUDEDIR, não é?

# Report lapacke header search locations.
set(CBLAS_INCLUDE_DIRS ${_CBLAS_PREFIX}/include)

(O comentário é açúcar em cima.)

Tenho a sensação de que a construção do CMake é muito menos madura do que se poderia pensar. O projeto é sério sobre ter isso como compilação principal ou é apenas uma contribuição direta? Estou realmente tentado a consertar o Makefile antigo, sem complicações. Mas agora eu gastei tanto tempo consertando as coisas do CMake, que eu detesto de qualquer maneira. Então, eu gostaria de acabar com isso.

Tenho que desistir agora ... consegui mover cblas.h para cblas.h.in conforme indicado acima e adicionei

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cblas.h.in cblas.h @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cblas_f77.h.in cblas_f77.h @ONLY)

para CBLAS / include / CMakeLists.txt, também tendo definido @HAVE_ILP64@ como 1 ou 0 no CMakeLists.txt de nível superior. Mas não consigo descobrir como fazer o material de instalação que está em um CMakeLists.txt de nível superior instalar os cabeçalhos gerados, ou a cópia estranha do mesmo de ${LAPACK_BINARY_DIR}/include (sério? A copiar dentro da árvore de origem?)

O que a macro append_subdir_files deve fazer? Parece acrescentar uma cópia do prefixo aos caminhos do cabeçalho. Não tenho caminho suficiente ou muito caminho para os arquivos de cabeçalho de origem. Eu só quero instalar os arquivos de cabeçalho de AQUI para LÁ, caramba.

Alguém experiente pode ajudar aqui? Acho que poderia descobrir isso amanhã, mas não tenho certeza se isso sem quebrar algo no mundo real para alívio emocional.

sim. Concordo e prefiro a solução que não replique o cabeçalho inteiro. Eu acho que é mais limpo.

Isso é ambíguo para mim. Qual é a solução mais limpa? O que estou preparando agora é:

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

O CMakeFile deve substituir HAVE_ILP por 1 ou 0, o cabeçalho resultante sendo instalado para a construção atual.

(A propósito: long não funcionaria no Windows. É long long there ... ou int64_t em todas as plataformas com stdint.)

Direito. Acabei de instalar as bibliotecas de 64 bits (BUILD_INDEX64 = ON) e não consegui ver nada me dizendo para usar WeirdNEC , LAPACK_ILP64 ou HAVE_LAPACK_CONFIG_H . Obrigado por notar isso!

Estou imaginando um futuro onde você faz

cc -I/foo/include/netlib64 -o bar bar.c -L/foo/lib -lcblas64

E as coisas são tratadas em foo / include / netlib64 / cblas.h, caso contrário, por foo / include / netlib / cblas.h (possivelmente vinculado a foo / include / cblas.h).

Tenho a suspeita de que _não_ é isso que você quis dizer, mas quero convencer que é melhor ;-)

Desculpe, deixe-me explicar. No início, gostei da ideia de manter o cabeçalho original cblas.h e criar include/netlib64/cblas.h e include/netlib/cblas.h com algo como

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include <cblas.h>

Você pode tentar não duplicar o cabeçalho colocando 'o' cabeçalho em /foo/include/cblas.he fazer com que /foo/include/netlib64/cblas.h inclua aquele apenas definindo WeirdNEC, mas isso significa que o 64 pacotes de bits e de 32 bits compartilham esse arquivo de cabeçalho comum, que é complicado para o empacotamento. É muito melhor se cada um colocar seu arquivo em lugares / nomes separados. O nome precisa permanecer cblas.h porque você não quer sair por aí substituindo #include <cblas.h> linhas.

Edit: Além disso, ter cblas.h include ../cblas.h é confuso por si só. Também definimos o diretório de instalação do cabeçalho _one_ para cmake.

mas sim, teríamos que usar include/netlib64 e include como diretórios de inclusão se um cabeçalho incluir o outro.

Por padrão, é / foo / include, não / foo / netlib64 / include. Não vou mudar esse padrão. Os empacotadores terão que especificar o subdiretório como este (BSD make no pkgsrc):

.if !empty(LAPACK_COMPONENT:M*64)
.  if empty(MACHINE_ARCH:M*64)
PKG_FAIL_REASON+=       "${LAPACK_COMPONENT} incompatible with non-64-bit platform"
.  endif
HEADERDIR=netlib64
.else
HEADERDIR=netlib
.endif

# Note: We patch the build to install both static and
# shared libraries.
CMAKE_ARGS=     -DBUILD_DEPRECATED=ON \
                -DBUILD_SHARED_LIBS=ON \
                -DBUILD_STATIC_LIBS=ON \
                -DCMAKE_INSTALL_INCLUDEDIR=${PREFIX}/include/${HEADERDIR} \
                ${LAPACK_COMPONENT_CMAKE_ARGS}

Isso me parece bom. Portanto, você apenas adicionaria uma alternativa para construir o LAPACK sem ter que _ adivinhar_ os sinalizadores do compilador. Mas a forma atual também funcionaria.

(A propósito: long não funcionaria no Windows. É long long there ... ou int64_t em todas as plataformas com stdint.)

Bom saber. BLAS ++ e LAPACK ++ usam int64_t em vez de long long.

@weslleyspereira Então você a princípio gostou da ideia:

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include "../cblas.h"

com /prefix/include/cblas.h e /prefix/include/netlib64/cblas.h, o último localizando o primeiro? Mas você concorda agora que é uma solução mais robusta instalar um cabeçalho parecido com este para uma compilação de 64 bits?

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

(long vs. int64 é um assunto diferente, mas eu sou totalmente a favor dessa mudança, assim como o BLAS ++)

Caramba, eu não tenho certeza se é seguro assumir que `#include" ... / cblas.h "encontrará apenas o outro cabeçalho indentado. O padrão C parece dizer que a ordem de pesquisa é definida pela implementação, não necessariamente em relação ao cabeçalho atual. Meu principal problema como empacotador é que eu precisaria de um pacote separado para esse cabeçalho comum ou faria com que o pacote de 64 bits dependesse do de 32 bits apenas para isso. Isso seria uma merda.

Eu realmente gostaria de avançar agora com essa mudança para o pkgsrc, para definir uma mudança para o código upstream mais tarde. Poderíamos discutir um novo símbolo para forçar índices de 32 bits ou índices de 64 bits explicitamente com qualquer um dos cabeçalhos ( -DNETLIB_INDEX_BITS=64 ?), Apenas padronizando com o que a biblioteca foi construída.

Posso chegar a um acordo sobre a nossa solução pretendida sendo esta?

lib/libcblas64.so
include/optional_subdir64/cblas.h

e

lib/libcblas.so
include/optional_subdir/cblas.h

Cada construção do código LAPACK resulta em cabeçalhos que, pelo menos por padrão, correspondem às bibliotecas instaladas sem que o usuário defina nada. OK?

Então, eu poderia inserir isso antes do próximo lançamento do pkgsrc (prazo final se aproximando) e podemos discutir mais detalhadamente os detalhes dessa implementação para que eu possa descartar os patches após mesclar algo aqui, com uma nova versão do LAPACK. Com esta mudança, a compilação simples do Makefile também precisa de conserto, mas eu não preciso disso para os _my_ patches ainda quando uso apenas a compilação do CMake.

(Só preciso de alguma forma checar meu temperamento ao tentar vencer aquele estranho build do CMake até o envio, onde ele embaralha as cópias dos cabeçalhos nos diretórios de construção e não consegue encontrá-los para instalação. Ou decidir se esses arquivos .cmake quebrados têm alguma utilidade para nós, talvez apenas removê-los da instalação ... temos o pkg-config!)

Nada? Devo admitir que não vejo muita chance de uma solução diferente na prática, pois este é o exemplo dado pelo openblas, a principal implementação que usamos. Posso imaginar convencer a Intel a ter subdiretório para cabeçalhos de índice de 64 bits / 32 bits também, envolvendo seus mkl_cblas.h e mkl_lapacke.h. Caso contrário, construo um pacote simples que apenas fornece isso.

include/mkl-blas/cblas.h
include/mkl-blas64/cblas.h

Atualmente, adicionei máquinas ao pkgsrc para fornecer compilações com a linha -DWeirdNEC -DHAVE_LAPACK_CONFIG_H -DLAPACK_ILP64 engraçada, com cblas e cblas64 instalando cabeçalhos idênticos. Pode continuar assim, mas ainda acho que faz sentido ter o cabeçalho configurado para corresponder à ABI de construção.

@weslleyspereira Então você a princípio gostou da ideia:

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include "../cblas.h"

com /prefix/include/cblas.h e /prefix/include/netlib64/cblas.h, o último localizando o primeiro? Mas você concorda agora que é uma solução mais robusta instalar um cabeçalho parecido com este para uma compilação de 64 bits?

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

Sim é isso. Concordo com sua solução de ter subpastas para os cabeçalhos de 32 e 64 bits. Discuti isso com @langou , e ele também estava convencido de que essa seria uma boa solução.

(long vs. int64 é um assunto diferente, mas eu sou totalmente a favor dessa mudança, assim como o BLAS ++)

Direito. Isso deve ser tratado em outra edição.

Eu realmente gostaria de avançar agora com essa mudança para o pkgsrc, para definir uma mudança para o código upstream mais tarde. Poderíamos discutir um novo símbolo para forçar índices de 32 bits ou índices de 64 bits explicitamente com qualquer um dos cabeçalhos ( -DNETLIB_INDEX_BITS=64 ?), Apenas padronizando com o que a biblioteca foi construída.

Posso chegar a um acordo sobre a nossa solução pretendida sendo esta?

lib/libcblas64.so
include/optional_subdir64/cblas.h

e

lib/libcblas.so
include/optional_subdir/cblas.h

sim. Acho que você pode ir em frente e propor uma RP no futuro, obrigado! Pessoalmente, acho que um novo símbolo como NETLIB_INDEX_BITS faz todo o sentido. Gostaria apenas de ter certeza de que o valor padrão permanece 32, e que -DWeirdNEC implica -DNETLIB_INDEX_BITS=64 .

Cada construção do código LAPACK resulta em cabeçalhos que, pelo menos por padrão, correspondem às bibliotecas instaladas sem que o usuário defina nada. OK?

Parece bom para mim.

Então, eu poderia inserir isso antes do próximo lançamento do pkgsrc (prazo final se aproximando) e podemos discutir mais detalhadamente os detalhes dessa implementação para que eu possa descartar os patches após mesclar algo aqui, com uma nova versão do LAPACK. Com esta mudança, a compilação simples do Makefile também precisa de conserto, mas eu não preciso disso para os _my_ patches ainda quando uso apenas a compilação do CMake.

OK! Provavelmente teremos um lançamento de LAPACK no segundo semestre de 2021. E sim, o Makefile deve ser ajustado de acordo, e estou disposto a ajudar com isso.

Isso está um tanto relacionado. Não devemos esquecer que os cabeçalhos para netlib CBLAS não são fornecidos apenas por netlib ... NumPy sempre usa seu próprio cabeçalho:

https://github.com/numpy/numpy/blob/main/numpy/core/src/common/npy_cblas.h

E neste cabeçalho, ele define CBLAS_INDEX=size_t , diferente do tipo inteiro usado na especificação de índices. É usado apenas para os valores de retorno de algumas funções:

$ grep CBLAS_INDEX ./numpy/core/src/common/npy_cblas_base.h                                                                                                                                  
CBLAS_INDEX BLASNAME(cblas_isamax)(const BLASINT N, const float  *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_idamax)(const BLASINT N, const double *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_icamax)(const BLASINT N, const void   *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_izamax)(const BLASINT N, const void   *X, const BLASINT incX);

A diferença:

$ grep cblas_isamax ./numpy/core/src/common/npy_cblas_base.h  /data/pkg/include/cblas.h                                                                                                      
./numpy/core/src/common/npy_cblas_base.h:CBLAS_INDEX BLASNAME(cblas_isamax)(const BLASINT N, const float  *X, const BLASINT incX);
/data/pkg/include/cblas.h:CBLAS_INDEX cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);

Eu me pergunto se isso está possivelmente causando problemas. Para o Netlib, existe apenas um tipo de índice, enquanto outras implementações usam um tipo de valor de retorno diferente para as funções de índice. OpenBLAS dá o exemplo. Eles dizem que isamax retorna não assinado size_t, mas o wrapper C realmente chama uma função Fortran que retorna um inteiro assinado (Editar: uma sub-rotina que grava um valor inteiro assinado de 32 ou 64 bits para uma referência entregue a uma variável de 64 bits não assinada em 64 sistemas de bits).

A implementação de referência tem uma opinião sobre isso? _Acho que não há nenhum problema real, pois um valor size_t sempre será capaz de conter qualquer retorno não negativo de isamax (). Mas tem um cheiro duvidoso. (Editar: você poderia construir com índices de 64 bits em um sistema de 32 bits em que size_t é 32 bits, certo? Então você obteve estouro. Além da dificuldade de lançar size_t * para int * .)

Visto que as implementações otimizadas parecem ter decidido size_t ali, a referência deve aceitar esse fato e seguir?

E quão perigoso é, na verdade, vincular numpy com cblas de referência?

OpenBLAS dá o exemplo. (...)
Visto que as implementações otimizadas parecem ter decidido size_t ali, a referência deve aceitar esse fato e seguir?

Certamente não posso falar em nome de numpy (ou mkl etc., nesse caso), mas hesitaria em afirmar que o OpenBLAS é normativo de qualquer forma, muito menos em relação ao que é (acredito) geralmente visto como __a__ implementação de referência. .

Certo. É só que OpenBLAS ou MKL são o que as pessoas usam na prática e ambos parecem ter se estabelecido

#define CBLAS_INDEX size_t  /* this may vary between platforms */
#ifdef MKL_ILP64
#define MKL_INT MKL_INT64
#else
#define MKL_INT int
#endif
CBLAS_INDEX cblas_isamax(const MKL_INT N, const float  *X, const MKL_INT incX);

ou similarmente

#ifdef OPENBLAS_USE64BITINT
typedef BLASLONG blasint;
#else
typedef int blasint;
#endif
#define CBLAS_INDEX size_t
CBLAS_INDEX cblas_isamax(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);

vs. a referência

#ifdef WeirdNEC
   #define CBLAS_INDEX long
#else
   #define CBLAS_INDEX int
#endif
CBLAS_INDEX cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);

Como é que eles divergem da referência aqui? Houve comunicação sobre isso? Além disso ... vejo MKL e OpenBLAS definindo uma série de funções que nem mesmo fazem parte do CBLAS de referência:

CBLAS_INDEX cblas_isamin(const MKL_INT N, const float  *X, const MKL_INT incX);
CBLAS_INDEX cblas_idamin(const MKL_INT N, const double *X, const MKL_INT incX);
CBLAS_INDEX cblas_icamin(const MKL_INT N, const void   *X, const MKL_INT incX);
CBLAS_INDEX cblas_izamin(const MKL_INT N, const void   *X, const MKL_INT incX);

CBLAS_INDEX cblas_isamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

CBLAS_INDEX cblas_ismax(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

CBLAS_INDEX cblas_ismin(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

Portanto, estender o padrão é uma coisa, mas size_t vs int parece ser um problema sério em sistemas de 64 bits. Isso deve ser resolvido de alguma forma. Parece-me que o método Netlib é sensato: mesmo tipo que é usado para índices. Como todos chamam de rotinas Fortran como esta, no final

c     isamaxsub.f
c
c     The program is a fortran wrapper for isamax.
c     Witten by Keita Teranishi.  2/11/1998
c
      subroutine isamaxsub(n,x,incx,iamax)
c
      external isamax
      integer  isamax,iamax
      integer n,incx
      real x(*)
c
      iamax=isamax(n,x,incx)
      return
      end

… Entregando um endereço de size_t para iamax, isso parece errado. Não encontrei outra implementação além desta de referência nas fontes do OpenBLAS. Eles são apenas estúpidos mudando o tipo externo assim ou estou negligenciando algo muito básico? Alguém está realmente usando essas funções?

Olá a todos, Referência BLAS, referência CBLAS, referência LAPACK, dois dos principais impulsos desses projetos são (1) algoritmos numéricos e (2) definição de interfaces comuns, uma implementação de referência e um conjunto de testes que vai assim. Acho que todos os envolvidos nesses projetos ficam felizes em ver e aprender com outros projetos (OpenBLAS, MKL, etc.) sobre engenharia de software, melhores práticas para implantação de software, etc. Temos muito a aprender com esses projetos. (E também aprendemos muito com outros projetos de álgebra linear numérica!) De qualquer forma: referência BLAS, CBLAS, LAPACK pode usar alguma melhoria em seu pacote CMake, interfaces, e se OpenBLAS (por exemplo) tem um processo melhor, que é bem adequado para nós, bem, eu sou totalmente a favor de mudarmos em direção a este modelo.

Para contextualizar, o CBLAS nasceu de um comitê (Fórum Técnico de Subprogramas de Álgebra Linear Básica) que trabalhou de 1996 a 2000 na revisitação do BLAS, como parte disso definiu uma interface C para o BLAS. Ver:
http://www.netlib.org/blas/blast-forum/
Em particular, veja:
http://www.netlib.org/blas/blast-forum/cinterface.pdf
Acredito que o CBLAS oferecido pelo LAPACK é uma implementação da interface definida pelo Fórum Técnico de Subprogramas de Álgebra Linear Básica 25 anos atrás.

Se houver sugestões para melhorar o CBLAS, envie-as junto. Posso tentar passar isso para as várias partes interessadas.

Obrigado pelo ponteiro. Portanto, a parte relevante parece ser B.2.2 nessa especificação, que diz que BLAS_INDEX geralmente é size_t , mas também pode ser escolhido para ser idêntico ao tipo inteiro Fortran (assinado) usado para indexação. Depende da implementação.

Portanto, parece que as implementações otimizadas populares escolheram size_t e a referência do Netlib escolheu o mesmo número inteiro que usa para Fortran. Vejo cópias de cblas.h em vários projetos que usam a lib (como numpy, enviar um cabeçalho para uma lib externa), com essa linha

#define CBLAS_INDEX size_t  /* this may vary between platforms */

Em https://github.com/LuaDist/gsl/blob/master/cblas/gsl_cblas.h , isso é acompanhado por

/* This is a copy of the CBLAS standard header.
 * We carry this around so we do not have to
 * break our model for flexible BLAS functionality.
 */

Parece que isso se originou na implementação de referência, mas mudou desde então? Olhando para 41779680d1f233928b67f5f66c0b239aecb42774… vejo que o switch CBLAS_INDEX com WeirdNEC estava lá antes da compilação de 64 bits. Uau, esse commit é recente. Agora vejo que size_t estava no referenc cblas.h até 2015, 83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 tendo mudado e introduzido o WeirdNEC define. Não imaginei que isso fosse tão recente! Extremamente confuso.

Também vejo que a versão anterior de cblas.h entregou int para a chamada de fortran, agora CBLAS_INDEX . Isso parece estar correto agora, com o uso consistente de CBLAS_INDEX como tipo inteiro e a mudança para 32 ou 64 bits na parte Fortran.

Mas será que as bibliotecas otimizadas que herdaram uma versão antiga de cblas.h com size_t mas sincronizam as fontes com o código CBLAS atual da referência, têm um bom bug? Eles não estão fazendo algo assim para o caso de 32 bits em um sistema de 64 bits?

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


void ia_max(int a, void *b)
{
    int *ib = (int*)b;
    *ib = a*2;
}


int main(int argc, char **argv)
{
    int i = atoi(argv[1]);
    size_t maxi;
    ia_max(i, &maxi);
    printf("in %d out %ld\n", i, (long)maxi);
    return 0;
}

Isto resulta em

$ gcc -O -o t t.c
$ ./t 42
in 42 out 140724603453524

Inicializar o size_t para zero ajuda, mas provavelmente apenas no caso de little-endian. Ninguém se mete em problemas por isso? Eu tenho que estar faltando alguma coisa.

Concluir:

  1. A referência CBLAS tinha size_t como valor de retorno primeiro.
  2. Ele usou int na chamada real para Fortran, no entanto.
  3. Downstream (usuários otimizados de BLAS, CBLAS) executado com a versão antiga do cabeçalho.
  4. Referência CBLAS apresenta hack WeirdNEC para um sistema específico, substituindo size_t por int ou long (combinando com o lado Fortran ?!)
  5. A interface CBLAS de referência de 64 bits é construída em cima disso, usando CBLAS_INDEX todos os lugares para o inteiro padrão do Fortran.
  6. Os downstreams fizeram suas próprias coisas com suporte de 64 bits, mas separando-o de CBLAS_INDEX , que é sempre size_t.
  7. Downstreams herdam os invólucros CBLAS que empregam CBLAS_INDEX para chamar Fortran que espera o número inteiro padrão.

Como resultado, isso soa como uma ruptura maravilhosa. Cabeçalhos e código divergiram. Por que ninguém percebeu problemas ainda? Ou perdi a parte em que o código do wrapper CBLAS de referência para isamax e amigos não é realmente usado?

O OpenBLAS pelo menos não usa o código wrapper CBLAS do Reference-LAPACK (e nunca o fez, a fonte está lá, mas não foi construída)

@ martin-frbg Bom saber. Você pode apontar um caminho de código para, digamos, x86-64 que mostra como o size_t é passado para o cálculo real para cblas_isamax ()? Encontrei algumas implementações específicas do kernel, mas não tenho certeza sobre o caso geral.

Seria bom saber que ninguém realmente passa um (size_t*) para a interface do Fortran.

Com certeza não é bom que os projetos simplesmente presumam

size_t cblas_isamax(…)

quando a biblioteca real pode oferecer um int ou long (ou int64_t) como valor de retorno. Pode funcionar na maioria das vezes com valores em registradores de 64 bits, mas não é legal. Podemos retificar isso nas implementações? As pessoas não seguiram o exemplo do Netlib nos últimos 5 anos sobre o uso consintente de CBLAS_INDEX .

o código relevante está em OpenBLAS / interface, por exemplo, interface / imax.c é compilado em cblas_isamax () quando CBLAS é definido, nenhum código Fortran envolvido em seu gráfico de chamada.

Ah bom. Portanto, o único caso que é realmente problemático é depender de projetos usando uma cópia de cblas.h que não cabe na biblioteca.

Não encontro o uso real de cblas_isamax() e amigos no NumPy (e no SciPy), então isso pode ser apenas um problema teórico. Mesmo assim, deve ser corrigido. Então:

  1. Outros seguem o exemplo do Netlib de usar int32_t / int64_t (vamos ser explícitos ;-) BLAS_INDEX para retornos de tamanho e argumentos de índice.
  2. O Netlib cede e reverte para size_t para esses retornos como os outros.

Este é um assunto separado para discutir? No entanto, ele depende da escolha da biblioteca de 32 ou 64 bits.

PS: Ainda não tenho certeza se enums na API são uma boa ideia (como tipo de dados real para argumentos de função e membros de estrutura), pois há opções do compilador para alterar o inteiro usado abaixo deles. Não é tão relevante na prática, mas mesmo assim me deixa inquieto.

Quanto mais penso nisso, mais me inclino para a Opção 2: tínhamos size_t na API por muito tempo. Então o Netlib mudou esse size_t para int ou long. Independentemente do que melhor corresponda ao código Fortran ou seja mais consistente, o size_t foi estabelecido API e a referência do Netlib quebrou isso.

Devo abrir um PR sobre como mudar as coisas para

size_t cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);
size_t cblas_idamax(const CBLAS_INDEX N, const double *X, const CBLAS_INDEX incX);
size_t cblas_icamax(const CBLAS_INDEX N, const void   *X, const CBLAS_INDEX incX);
size_t cblas_izamax(const CBLAS_INDEX N, const void   *X, const CBLAS_INDEX incX);

novamente? Não deve haver mais macro nesta posição para enfatizar que é sempre size_t, em todos os lugares, no passado e no futuro.

Em https://github.com/numpy/numpy/issues/19243 , basicamente chegamos a: „Parafuso Netlib, size_t funciona para todos os outros“.

Existem três razões para usar size_t :

  1. Todas as funções de biblioteca padrão C e C ++ aceitam e retornam esse valor, por exemplo, void* malloc(size_t) , size_t strlen() ou std::size_t std::vector<T>::size() (C ++). Usar size_t evita valores truncados e conversões assinadas / não assinadas.
  2. size_t é freqüentemente usado para expressar quantidades que não podem ser negativas, por exemplo, dimensões da matriz.
  3. Os padrões C e C ++ garantem que você pode armazenar o tamanho de qualquer array em um size_t e que você pode indexar todos os elementos com size_t , cf. cppference.com: size_t .

Edit: Você poderia construir com índices de 64 bits em um sistema de 32 bits onde size_t é 32 bits, certo? Então você tem overflow.

Não, porque um sistema de 32 bits pode ter mais de 4 GB de memória virtual (o Linux oferece suporte), mas um único processo de 32 bits nunca pode acessar mais de 4 GB. Ou seja, os 32 bits superiores dos índices de 64 bits nunca são usados.

_Limite de memória para um processo de 32 bits em execução em um sistema operacional Linux de 64 bits_

Também estou pensando que manter size_t é a coisa certa a fazer, porque mudar dele foi a quebra de ABI e tirou o Netlib de sincronia com o resto do mundo.

Mas sinto-me compelido a criticar seus argumentos ;-)

1. All of the C and C++ standard library functions accept and return this value

Quando pesquisei isso, tropecei na admissão de que era um erro histórico usar um tipo não assinado para índices de contêiner C ++ e provavelmente até mesmo o tipo de retorno do método size (), já que você rapidamente acaba misturando números assinados e não assinados em de alguma maneira. O estado atual do Netlib seria consistente consigo mesmo, sempre usando tipos assinados para tamanho e índices, mas é claro inconsistente com malloc() , que tem um requisito de tamanho não assinado para realmente ser capaz de endereçar toda a memória que cabe em 32 bits (ou 64 bits, em teoria).

Estou me perguntando direito sobre isso no código que escrevi, onde eventualmente entrego um índice como deslocamento para uma chamada de função. O índice não tem sinal, o deslocamento está assinado. Além de os compiladores (MSVC) serem confundidos com -unsigned_value , isso significa que sempre tenho que me preocupar com um possível estouro na conversão.

Mas de qualquer maneira, se for apenas sobre o cálculo de tamanhos de memória disponíveis para malloc () e amigos, size_t é a coisa natural, e já esteve lá antes no CBLAS.

Sobre possíveis problemas com o estado atual do código, incompatibilidade com cblas.h vendidos em compilações:

Não, porque um sistema de 32 bits pode ter mais de 4 GB de memória virtual (o Linux oferece suporte), mas um único processo de 32 bits nunca pode acessar mais de 4 GB. Ou seja, os 32 bits superiores dos índices de 64 bits nunca são usados.

Certo, size_t permanece em 32 bits. Quando você (por mais bobo que seja) construiu cblas_isamax() para retornar um inteiro de 64 bits, após hackear a construção para não usar long , mas int64_t , é claro, o que realmente acontecer em tal uso?

size_t cblas_isamax(); // really int64_t cblas_isamax()!
size_t value = cblas_isamax(…);

A convenção de chamada x86 pode colocar o valor de 64 bits em EAX e EDX. Ou pode funcionar com um retorno de ponteiro e algum buffer. Mas o que outras arquiteturas fariam? Portanto, você pode não obter corrupção, mas com certeza um valor errado. O melhor caso são os 32 bits mais altos sendo ignorados.

Agora imagine um sistema big-endian de 32 bits (alguma forma de ARM) ... tem certeza de que obterá a metade desejada do valor de volta?

Você realmente não poderia trabalhar com dados não esparsos que precisam de índices de 64 bits no programa de 32 bits, com certeza. Mas apenas ser capaz de fazer uma chamada de função não correspondente que _at_least_ forneça resultados errados não parece saudável.

Fiz alguns testes rápidos… no Linux x86 ( gcc -m32 em um sistema x86-64), você apenas elimina os 32 bits superiores.

O caso mais interessante ... 64 bits size_t:

size_t cblas_isamax(); // really int32_t cblas_isamax()!
size_t value = cblas_isamax(…);

Novamente, no x86-64, a relação peculiar entre RAX de 64 bits e EAX de 32 bits torna as coisas um tanto bem definidas para também zerar silenciosamente os 32 bits superiores, uma vez que você faz uma operação de 32 bits no registro compartilhado. Mas é divertido ter uma definição de função um pouco estranha:

$ cat ret32.c 
#include <stdint.h>

int32_t ret64(int64_t a)
{
    a += 1LL<<32;
    return a;
}
$ gcc -m64  -g -c -o ret32.o ret32.c 
$ LANG=C objdump -S ret32.o 
[…]
   8:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
    a += 1LL<<32;
   c:   48 b8 00 00 00 00 01    movabs $0x100000000,%rax
  13:   00 00 00 
  16:   48 01 45 f8             add    %rax,-0x8(%rbp)
    return a;
  1a:   48 8b 45 f8             mov    -0x8(%rbp),%rax

Você pode argumentar se é inteligente para o compilador trabalhar no registro de 64 bits completo e deixar os 32 bits superiores não claros para uma função que deve retornar um valor de 32 bits, mas é perfeitamente legal se você confiar apenas no chamador usando os 32 bits inferiores, eu acho.

$ cat call.c 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

INDEX ret64(int64_t);

int main(int argc, char **argv)
{
    if(argc < 2)
        return 1;
    int64_t a = (int64_t)strtoll(argv[1], NULL, 10);
    INDEX  s = ret64(a);
    printf("%lld\n", (long long)s);
    return 0;
}
$ gcc -m64 -g -DINDEX=int32_t -c -o call32_64.o call.c
$ gcc -m64 -g -DINDEX=size_t -c -o call64_64.o call.c
$ ./call32_64 1
1
$ ./call64_64 1
4294967297

Diversão. Um valor de retorno de 32 bits que fornece mais do que é possível em 32 bits. Isso é o que pode acontecer (em princípio) com o estado atual do Netlib CBLAS sendo vinculado ao código que espera size_t. Eu acho que os 32 bits superiores de RAX serão zero no código real na prática. Mas quem sabe ... o compilador espera que o chamador não use mais do que os 32 bits inferiores em qualquer plataforma ... pode muito bem armazenar lixo lá.

Então ... estamos concordando em mover o Netlib de volta para size_t como valor de retorno?

Obrigado por todos esses comentários valiosos!

Eu discuti esse tópico um pouco com @langou. Com base na discussão aqui, minha proposta é:

Em um PR separado:

  1. Voltamos para cblas.h que usa duas definições de inteiros, digamos CBLAS_INDEX e CBLAS_INT. É o que acontece no MKL (CBLAS_INDEX e MKL_INT) e no OpenBLAS (CBLAS_INDEX e blasint). CBLAS_INDEX será usado apenas no retorno de i*amax . Com isso, restauramos um ABI compatível com outro BLAS.
  2. Além disso, escolhemos o valor padrão de CBLAS_INDEX como size_t e coletamos as opiniões da comunidade.

Acho que essa ideia está alinhada (ou talvez seja a mesma) por trás das discussões recentes neste tópico.
Como @drhpc apontou,
https://github.com/Reference-LAPACK/lapack/commit/83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 alterou o valor padrão de CBLAS_INDEX, e
https://github.com/Reference-LAPACK/lapack/commit/41779680d1f233928b67f5f66c0b239aecb42774 alterou o uso de CBLAS_INDEX.

Só para reforçar:

  • OpenBLAS, MKL, GNU Scientific Library e Numpy usam size_t por padrão.
  • A interface C para BLAS (https://www.netlib.org/blas/blast-forum/cinterface.pdf) indica que, normalmente, CBLAS_INDEX = size_t .

Você concorda? Se você fizer isso, posso abrir o PR. Ou talvez @drhpc queira fazer isso.

Eu concordo. E, por favor, vá em frente com o PR.

@ mgates3 mencionou a discussão no Slate Google Group:
https://groups.google.com/a/icl.utk.edu/g/slate-user/c/f5y6gt0aoLs/m/oQyyhikwCgAJ
A discussão não é sobre o que "CBLAS_INDEX" deve ser, mas mais sobre o que "CBLAS_INT" deve ser. CBLAS_INT deve ser size_t ou inteiro assinado ou etc.? Acho que os participantes estão apresentando bons pontos, então estou passando adiante.

Por favor, consulte # 588.

Certo, size_t permanece em 32 bits. Quando você (por mais bobo que seja) construiu cblas_isamax() para retornar um inteiro de 64 bits, após hackear a construção para não usar long , mas int64_t , é claro, o que realmente acontecer em tal uso?

size_t cblas_isamax(); // really int64_t cblas_isamax()!
size_t value = cblas_isamax(…);

A convenção de chamada x86 pode colocar o valor de 64 bits em EAX e EDX. Ou pode funcionar com um retorno de ponteiro e algum buffer. Mas o que outras arquiteturas fariam? Portanto, você pode não obter corrupção, mas com certeza um valor errado. O melhor caso são os 32 bits mais altos sendo ignorados.

Agora imagine um sistema big-endian de 32 bits (alguma forma de ARM) ... tem certeza de que obterá a metade desejada do valor de volta?

Este é o fim do jogo. Em CPUs Arm de 32 bits, quatro valores de 32 bits podem ser passados ​​e retornados em registradores, os valores de 64 bits ocupam dois registradores consecutivos, consulte a Seção 6.1.1.1 em _Padrão de chamada de procedimento para a arquitetura Arm_ . Em vez de gravar em um registrador, o receptor irá confundir dois registradores com seus inteiros de 64 bits; isso é obviamente um problema. Assim que o chamador ficar sem registros para parâmetros, a pilha é usada. O alinhamento da pilha é de 32 bits, mas em vez de ler ou gravar 32 bits, o receptor escreve 64 bits; novamente, o jogo acabou e esse problema (incompatibilidade de tamanhos de leitura / gravação da pilha) deve causar problemas em todas as arquiteturas de conjunto de instruções em algum ponto.

Estou me perguntando direito sobre isso no código que escrevi, onde eventualmente entrego um índice como deslocamento para uma chamada de função. O índice não tem sinal, o deslocamento está assinado. Além de compiladores (MSVC) serem confundidos por -unsigned_value, isso significaria que eu sempre terei que me preocupar com um possível estouro na conversão.

Não, os comitês padrão por trás de C e C ++ fazem seu código se comportar da maneira óbvia neste caso: Se u é um valor sem sinal e s é um valor assinado, onde u tem pelo menos tantos bits quanto s , então u + s produzirá o resultado matematicamente correto, a menos que u + s over- ou underflows. Se for under- / overflows, o resultado será agrupado, ou seja, (u + s) mod 2^b , onde b é o número de bits em u e s . Por outro lado, se o tipo com sinal pode representar todos os valores do tipo sem sinal, então o valor sem sinal será convertido para o tipo sem sinal.

As cláusulas relevantes no rascunho da norma C11 são as seguintes:

  • 6.2.5.9: Operações binárias apenas com operandos sem sinal não podem estourar; o resultado é o módulo MAX + 1 , onde MAX é o maior valor representável.
  • 6.3.1.3: Dado um valor com sinal s , ele é convertido para o valor sem sinal s se s >= 0 , caso contrário, é convertido para s + MAX + 1 .
  • 6.3.1.8: Operandos assinados e não assinados [do mesmo tamanho] são convertidos em não assinados; um operando sem sinal é convertido em um tipo com sinal se o tipo com sinal pode representar todos os valores do tipo sem sinal

Portanto, u + s (sintaxe C) será avaliada para

  • (u + s) mod (M + 1) se s >= 0 ,
  • (u + s + M + 1) mod (M + 1) caso contrário.

Na ausência de estouro excessivo ou insuficiente, essa expressão será avaliada como u + s que é o resultado desejado intuitivamente.

Quando pesquisei isso, tropecei na admissão de que era um erro histórico usar um tipo não assinado para índices de contêiner C ++ e provavelmente até mesmo o tipo de retorno do método size (), já que você rapidamente acaba misturando números assinados e não assinados em de alguma maneira.

Existem alguns programadores C ++ (incluindo o inventor do C ++) que estão propondo o uso de inteiros assinados em todos os lugares, consulte as Diretrizes Básicas do

  • verificação de valores mínimos: com um inteiro sem sinal é em muitos casos supérfluo verificar o valor mínimo, com um inteiro com sinal é obrigatório; isso está sujeito a erros e pode causar problemas de segurança, consulte, por exemplo, CWE-839 _ Comparação de faixa numérica sem verificação mínima_ .
  • estouro: um estouro não assinado tem um resultado bem definido, enquanto um estouro inteiro assinado constitui um comportamento indefinido.

Você pode tentar verificar se há um estouro assinado com a expressão a + b < a mas o compilador pode otimizá-lo sem aviso, consulte por exemplo bug GCC 30475 _assert (int + 100> int) otimizado fora_ de 2007. Isso funcionaria entretanto, com inteiros não assinados ( a não assinados, b possivelmente assinados e b tendo no máximo tantos bits quanto a ). Vendo o artigo _OptOut - Otimizações de comportamento indefinido do compilador_ de 2020, o comportamento do GCC aparentemente não mudou.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

hokb picture hokb  ·  16Comentários

h-vetinari picture h-vetinari  ·  8Comentários

Dichloromethane picture Dichloromethane  ·  11Comentários

nboelter picture nboelter  ·  3Comentários

Peter9606 picture Peter9606  ·  7Comentários