Lapack: ¿Permitir una posible instalación de la biblioteca index-64 junto con la biblioteca index-32 estándar?

Creado en 1 nov. 2020  ·  41Comentarios  ·  Fuente: Reference-LAPACK/lapack

Actualmente, debian tiene (algunos) encabezados separados para blas64 y cblas64 (aunque no de la implementación de referencia).

No estoy seguro de si son correctos o no, con respecto a la API index64 de referencia (son de la biblioteca blis).

¿Sería posible agregar una opción a cmake, algo como BUILD_INDEX64 , que está predeterminado en OFF , pero si se ha activado, creará las bibliotecas index-64?
Si hago un PR para tal opción, ¿se consideraría una posibilidad?

Algunas cosas que tenía en mente para permitir que esto coexista con la instalación estándar: nombre las bibliotecas a libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so , de esta manera no hay conflicto entre los nombres de las bibliotecas (aunque, por supuesto, no puede vincular con libblas y libblas64 al mismo tiempo).
Además, la biblioteca debería compilarse dos veces, una para index32 y otra para index64 (pero ese es un escenario perfectamente normal y no un factor decisivo).
El único conflicto que no puedo pensar en una buena resolución son los nombres de los archivos de encabezado.
Si sigue el estilo de Debians, sería conveniente llamar también a los encabezados c que terminan en 64.
(Estoy manteniendo esto para Gentoo y me gustaría mantener el ecosistema muy cerca de Debian, para que tengamos problemas mínimos para que los desarrolladores cambien de sistema)

Estoy abierto a cualquier sugerencia antes de hacer el PR: corazón:

Gracias,
Aisha

Build System Enhancement

Todos 41 comentarios

Hola Aisha, eso tiene mucho sentido para mí, pero veamos si tenemos comentarios de otros. Así que esperemos unos días. J

Definitivamente, suena como un buen plan.

Dios mío @langou
eres tan rápido: corazón:

Solo para completar, estoy escribiendo cosas que aún tenemos que hacer:

  • Descubra cómo nombrar los encabezados para que la API de 32 bits pueda coexistir con la API de 64 bits
  • Corrija las declaraciones printf / fprintf para que utilicen el calificador correcto para la impresión.

Cualquier sugerencia para resolver el primer punto es bienvenida, lamentablemente no tengo una solución "limpia".

Un par de preguntas que me ayudarán a manejar el nombre de los archivos.

  • Parece haber una tonelada de definiciones duplicadas entre cblas_f77.h y cblas_test.h . ¿Realmente lo necesitamos?
  • ¿Es necesario instalar cblas_test.h ? Dado su nombre (y los archivos en los que se usa), supongo que se usará solo durante la fase de prueba. ¿Quizás no deberíamos instalar este archivo a nivel de todo el sistema?

Hola @ epsilon-0,

Algunas cosas que tenía en mente para permitir que esto coexista con la instalación estándar: nombre de las bibliotecas libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so, de esta manera no hay conflicto entre los nombres de las bibliotecas ( aunque, por supuesto, no se puede vincular con libblas y libblas64 al mismo tiempo).

es posible que esté buscando PR # 218. El autor de este PR es Björn Esser del Proyecto Fedora.

Hola @ epsilon-0. ¿El # 462 resolvió este problema?

@weslleyspereira no, esto aún no está completo.
Es necesario que se realicen más cambios de nombre / manejo de encabezados.
Estoy ocupado durante las próximas semanas, así que no podré hacer esto pronto.
Esquema básico

  • los archivos de encabezado deben llamarse cblas.h y cblas64.h , al igual que para otros encabezados

    • esto significa que los archivos *.c necesitarían un ligero ajuste para incluir el encabezado adecuado, pero esto es solo durante el tiempo de compilación, por lo que se puede piratear.

  • los archivos cmake deben instalarse en lapack64 o cblas64 , etc.

OK veo. ¡Gracias por el rápido seguimiento!

Tengo problemas similares al tratar de empaquetar cosas con pkgsrc. Me gustaría tener una instalación completa de la referencia, con cblas y lapacke. Para diferentes implementaciones que se instalan al mismo tiempo, me conformé con diferentes nombres de biblioteca y subdirectorios para los encabezados, por ejemplo

/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)

(Etcétera)

No consideramos el cambio de tiempo de ejecución como las distribuciones binarias, por lo que está bien si cada cblas.h (y lapacke.h) es específico de su biblioteca coincidente, como con nombres adicionales para libopenblas. La selección del tiempo de construcción se realiza a través de

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

(etc.) Eso es lo que se supone que dicen los archivos .pc, y es mucho más fácil que comunicar un nombre de archivo de encabezado diferente. Todavía no son consistentes en eso, pero lo estoy arreglando. Parece que hasta ahora la gente simplemente ha pirateado eso en sus distribuciones, si es que se molestan con todas las bibliotecas de referencia.

Sin embargo, tengo una pregunta sobre esos encabezados.

Estoy pirateando la compilación de cmake para que cada componente se compile por separado y estoy intentando alguna otra reparación (consulte https://github.com/Reference-LAPACK/lapack/pull/556). Obtengo las bibliotecas libblas.so y libblas64.so bien construidas, configuro los directorios de encabezado ... pero cblas.hy lapacke.h instalados son idénticos para las versiones de indexación de 32 y 64 bits. Esto está en desacuerdo con openblas: Ahí, tengo una diferencia crucial que no veo para las compilaciones de 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 las bibliotecas de referencia, todos los encabezados de las compilaciones de índices de 32 y 64 bits son idénticos y aparentemente se espera que los usuarios pongan
-DWeirdNEC en sus banderas (podría haber sido divertido hace 30 años) para cblas.hy -DLAPACK_ILP64 -DHAVE_LAPACK_CONFIG_H . Dado que la gente usa las bibliotecas BLAS optimizadas en producción, el estándar de facto es no exponer eso a los usuarios. Estos se retroalimentan en la referencia, en mi humilde opinión, y los encabezados instalados desde una compilación de ILP64 no deberían requerir banderas funky para evitar que su aplicación se bloquee cuando se vincula a la biblioteca de 64 bits.

¿Estamos de acuerdo en que es la solución correcta modificar los encabezados en el momento de la compilación para definir los enteros correctos?

Por cierto, los archivos de configuración de cblas que están instalados también pierden cualquier referencia a las definiciones necesarias, por lo que están rotos para las compilaciones de índice de 64 bits, como parece. Pero en realidad, considero no instalarlos en absoluto. Son redundantes con los archivos .pc y hacen posiblemente más difícil convencer a los paquetes dependientes que usan cmake para que acepten una opción de empaquetador a través de BLAS_LIBS etal.

PD: Con Intel MKL, hay un interruptor central -DMKL_ILP64 para configurar. Me imagino montando trivial
include/intel-mkl64/cblas.h con

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

para ajustarse al esquema general. También podría poner la definición en BLAS_INCLUDES, lo mismo para las extrañas definiciones de netlib. ¿Qué es mejor? ¿Queremos hacerlo como Intel o como OpenBLAS?

¿Estamos de acuerdo en que es la solución correcta modificar los encabezados en el momento de la compilación para definir los enteros correctos?

Si. Estoy de acuerdo con eso y prefiero la solución que no replica todo el encabezado. Creo que está más limpio.

Por cierto, los archivos de configuración de cblas que están instalados también pierden cualquier referencia a las definiciones necesarias, por lo que están rotos para las compilaciones de índice de 64 bits, como parece.

Derecha. Acabo de instalar las bibliotecas de 64 bits (BUILD_INDEX64 = ON) y no pude ver nada que me diga que use WeirdNEC , LAPACK_ILP64 o HAVE_LAPACK_CONFIG_H . ¡Gracias por darte cuenta!

Si. Estoy de acuerdo con eso y prefiero la solución que no replica todo el encabezado. Creo que está más limpio.

Esto me resulta ambiguo. ¿Cuál es la solución más limpia? Lo que estoy preparando ahora es tal:

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

El CMakeFile reemplazará HAVE_ILP con 1 o 0, y el encabezado resultante se instalará para la compilación actual.

(Por cierto: long no funcionaría en Windows. Es mucho tiempo allí ... o int64_t en todas las plataformas con stdint).

Derecha. Acabo de instalar las bibliotecas de 64 bits (BUILD_INDEX64 = ON) y no pude ver nada que me diga que use WeirdNEC , LAPACK_ILP64 o HAVE_LAPACK_CONFIG_H . ¡Gracias por darte cuenta!

Me estoy imaginando un futuro donde tu lo haces

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

Y las cosas se manejan en foo / include / netlib64 / cblas.h, de lo contrario por foo / include / netlib / cblas.h (posiblemente vinculado a foo / include / cblas.h).

Tengo la sospecha de que esto _no_ es lo que querías decir, pero quiero convencerte de que es mejor ;-)

Puede intentar no duplicar el encabezado colocando 'el' encabezado en /foo/include/cblas.hy hacer que /foo/include/netlib64/cblas.h lo incluya solo definiendo WeirdNEC, pero eso significa que el 64 Los paquetes de bits y 32 bits comparten ese archivo de encabezado común, que es complicado para el empaquetado. Es mucho mejor si cada uno coloca su archivo en lugares / nombres separados. El nombre debe permanecer cblas.h porque no desea reemplazar las líneas #include <cblas.h> .

Editar: Además, tener cblas.h include ../cblas.h es complicado por sí mismo. También definimos el directorio de instalación del encabezado _one_ para cmake. Por defecto es / foo / include, no / foo / netlib64 / include. No voy a cambiar este valor predeterminado. Los empaquetadores tendrán que especificar el subdirectorio así (BSD make in 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}

Un aspecto hermoso de enviar / instalar el cblas.h de 32 bits con esta modificación a la ubicación habitual es que la mecánica original aún funciona. Solo la variante de 64 bits aplicará WeirdNEC. Puede decidir instalar solo el de 64 bits en un prefijo y mantener intactas las otras partes del ecosistema.

Oh, vamos ... el CBLAS / cmake / cblas-config-install.cmake.in parece olvidar -DCMAKE_INSTALL_INCLUDEDIR, ¿no?

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

(El comentario es azúcar en la parte superior).

Tengo la sensación de que la construcción de CMake es mucho menos madura como se podría pensar. ¿El proyecto toma en serio tener eso como construcción principal o es solo una contribución automática? Estoy realmente tentado a arreglar el Makefile antiguo, menos alboroto por todos lados. Pero ahora he dedicado tanto tiempo a arreglar las cosas de CMake, que de todos modos detesto. Así que me gustaría acabar con esto.

Tengo que rendirme ahora ... Me las arreglé para mover cblas.h a cblas.h.in como se indicó anteriormente, y agregué

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)

a CBLAS / include / CMakeLists.txt, también habiendo definido @HAVE_ILP64@ a 1 o 0 en el nivel superior CMakeLists.txt. Pero puedo, por mi vida, no averiguar cómo hacer que las cosas de instalación que están en un CMakeLists.txt de nivel superior instalen los encabezados generados, o la extraña copia de los mismos de ${LAPACK_BINARY_DIR}/include (¿en serio? copiar dentro del árbol de fuentes?)

¿Qué se supone que debe hacer la macro append_subdir_files? Parece anteponer una copia del prefijo a las rutas del encabezado. No obtuve suficiente o demasiada ruta a los archivos de encabezado de origen. Solo quiero instalar los archivos de encabezado de AQUÍ a ALLÍ, maldita sea.

¿Alguien con conocimientos puede ayudar aquí? Creo que podría resolverlo mañana, pero no estoy seguro de si será sin romper algo en el mundo real para obtener un alivio emocional.

Si. Estoy de acuerdo con eso y prefiero la solución que no replica todo el encabezado. Creo que está más limpio.

Esto me resulta ambiguo. ¿Cuál es la solución más limpia? Lo que estoy preparando ahora es tal:

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

El CMakeFile reemplazará HAVE_ILP con 1 o 0, y el encabezado resultante se instalará para la compilación actual.

(Por cierto: long no funcionaría en Windows. Es mucho tiempo allí ... o int64_t en todas las plataformas con stdint).

Derecha. Acabo de instalar las bibliotecas de 64 bits (BUILD_INDEX64 = ON) y no pude ver nada que me diga que use WeirdNEC , LAPACK_ILP64 o HAVE_LAPACK_CONFIG_H . ¡Gracias por darte cuenta!

Me estoy imaginando un futuro donde tu lo haces

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

Y las cosas se manejan en foo / include / netlib64 / cblas.h, de lo contrario por foo / include / netlib / cblas.h (posiblemente vinculado a foo / include / cblas.h).

Tengo la sospecha de que esto _no_ es lo que querías decir, pero quiero convencerte de que es mejor ;-)

Lo siento, déjame explicarte. Al principio, me gustó la idea de mantener el encabezado original cblas.h y crear include/netlib64/cblas.h y include/netlib/cblas.h con algo como

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

Puede intentar no duplicar el encabezado colocando 'el' encabezado en /foo/include/cblas.hy hacer que /foo/include/netlib64/cblas.h lo incluya solo definiendo WeirdNEC, pero eso significa que el 64 Los paquetes de bits y 32 bits comparten ese archivo de encabezado común, que es complicado para el empaquetado. Es mucho mejor si cada uno coloca su archivo en lugares / nombres separados. El nombre debe permanecer cblas.h porque no desea reemplazar las líneas #include <cblas.h> .

Editar: Además, tener cblas.h include ../cblas.h es complicado por sí mismo. También definimos el directorio de instalación del encabezado _one_ para cmake.

pero sí, tendríamos que usar include/netlib64 y include como directorios de inclusión si un encabezado incluye el otro.

Por defecto es / foo / include, no / foo / netlib64 / include. No voy a cambiar este valor predeterminado. Los empaquetadores tendrán que especificar el subdirectorio así (BSD make in 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}

Eso me parece bien. Por lo tanto, solo agregaría una alternativa para construir LAPACK sin tener que _ adivinar_ los indicadores del compilador. Pero la forma actual también funcionaría.

(Por cierto: long no funcionaría en Windows. Es mucho tiempo allí ... o int64_t en todas las plataformas con stdint).

Bueno saber. BLAS ++ y LAPACK ++ usan int64_t en lugar de long long.

@weslleyspereira Así que al principio te gustó esta idea:

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

con /prefix/include/cblas.hy /prefix/include/netlib64/cblas.h, ¿el último ubica al primero? ¿Pero está de acuerdo ahora en que es una solución más robusta instalar un encabezado que se ve así para una compilación 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 es un asunto diferente, pero estoy a favor de hacer ese cambio, al igual que BLAS ++)

Demonios, ni siquiera estoy seguro de si es seguro asumir que `#include" ... / cblas.h "encontrará solo el otro encabezado sangrado. El estándar C parece decir que el orden de búsqueda está definido por la implementación, no necesariamente en relación con el encabezado actual. Mi principal problema como empaquetador es que necesitaría un paquete separado para ese encabezado común o que el paquete de 64 bits dependa del de 32 bits solo para eso. Esto apestaría.

Realmente me gustaría seguir adelante ahora con un cambio de este tipo para pkgsrc, para resolver un cambio para el código ascendente más tarde. Podríamos discutir un nuevo símbolo para forzar índices de 32 bits o índices de 64 bits explícitamente con cualquiera de los encabezados ( -DNETLIB_INDEX_BITS=64 ?), Simplemente por defecto con lo que se construyó la biblioteca.

¿Puedo llegar a un acuerdo acerca de que nuestra solución prevista es esta?

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

y

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

Cada compilación del código LAPACK da como resultado encabezados que, al menos de forma predeterminada, coinciden con las bibliotecas instaladas sin que el usuario defina nada. ¿OK?

Luego podría deslizar esto antes del próximo lanzamiento de pkgsrc (la fecha límite se acerca) y podemos discutir más a fondo los detalles de esa implementación para poder eliminar los parches después de fusionar algo aquí, con un nuevo lanzamiento de LAPACK. Con este cambio, la compilación simple de Makefile también necesita corregirse, pero no la necesito para _my_ parches todavía cuando solo uso la compilación de CMake.

(Solo necesito controlar de alguna manera mi temperamento cuando trato de superar esa extraña compilación de CMake en la presentación, donde baraja las copias de encabezado alrededor de los directorios de compilación y luego no puede encontrarlas para instalarlas. O decidir si esos archivos .cmake rotos tienen algún uso para nosotros, tal vez simplemente elimínelos de la instalación ... ¡tenemos pkg-config!)

¿Cualquier cosa? Debo admitir que no veo muchas posibilidades de una solución diferente en la práctica, ya que este es el ejemplo de openblas, la implementación principal que usamos. Me puedo imaginar convencer a Intel de que también tenga un subdirectorio para los encabezados de índice de 64 bits / 32 bits, que envuelvan sus mkl_cblas.hy mkl_lapacke.h. De lo contrario, construyo un paquete simple que solo los proporciona.

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

Actualmente, agregué maquinaria a pkgsrc para proporcionar compilaciones con la divertida línea -DWeirdNEC -DHAVE_LAPACK_CONFIG_H -DLAPACK_ILP64 , con cblas y cblas64 instalando encabezados idénticos. Podría permanecer así, pero sigo pensando que tiene sentido tener el encabezado configurado para que coincida con la ABI de compilación.

@weslleyspereira Así que al principio te gustó esta idea:

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

con /prefix/include/cblas.hy /prefix/include/netlib64/cblas.h, ¿el último ubica al primero? ¿Pero está de acuerdo ahora en que es una solución más robusta instalar un encabezado que se ve así para una compilación de 64 bits?

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

Si eso es. Estoy de acuerdo con su solución de tener subcarpetas para los encabezados de 32 y 64 bits. Hablé de esto con

(long vs.int64 es un asunto diferente, pero estoy a favor de hacer ese cambio, al igual que BLAS ++)

Derecha. Esto debería abordarse en otro número.

Realmente me gustaría seguir adelante ahora con un cambio de este tipo para pkgsrc, para resolver un cambio para el código ascendente más tarde. Podríamos discutir un nuevo símbolo para forzar índices de 32 bits o índices de 64 bits explícitamente con cualquiera de los encabezados ( -DNETLIB_INDEX_BITS=64 ?), Simplemente por defecto con lo que se construyó la biblioteca.

¿Puedo llegar a un acuerdo acerca de que nuestra solución prevista es esta?

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

y

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

Si. Creo que puedes seguir adelante y proponer un PR en el futuro, ¡gracias! Personalmente, creo que un nuevo símbolo como NETLIB_INDEX_BITS tiene mucho sentido. Solo me aseguraría de que el valor predeterminado siga siendo 32, y que -DWeirdNEC implique -DNETLIB_INDEX_BITS=64 .

Cada compilación del código LAPACK da como resultado encabezados que, al menos de forma predeterminada, coinciden con las bibliotecas instaladas sin que el usuario defina nada. ¿OK?

Suena bien para mí.

Luego podría deslizar esto antes del próximo lanzamiento de pkgsrc (la fecha límite se acerca) y podemos discutir más a fondo los detalles de esa implementación para poder eliminar los parches después de fusionar algo aquí, con un nuevo lanzamiento de LAPACK. Con este cambio, la compilación simple de Makefile también necesita corregirse, pero no la necesito para _my_ parches todavía cuando solo uso la compilación de CMake.

¡OK! Probablemente tendremos un lanzamiento de LAPACK en el segundo semestre de 2021. Y sí, el Makefile debería ajustarse en consecuencia, y estoy dispuesto a ayudar con eso.

Esto está algo relacionado. No debemos olvidar que los encabezados de netlib CBLAS no solo los proporciona netlib ... NumPy siempre usa su propio encabezado:

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

Y en este encabezado, establece CBLAS_INDEX=size_t , diferente del tipo de entero utilizado para especificar índices. Se utiliza únicamente para los valores de retorno de algunas funciones:

$ 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);

La diferencia:

$ 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);

Me pregunto si es posible que eso esté causando problemas. Para Netlib, solo hay un tipo de índice, mientras que otras implementaciones usan un tipo de valor de retorno diferente para las funciones de índice. OpenBLAS da el ejemplo. Dicen que isamax devuelve unsigned size_t, pero el contenedor de C en realidad llama a una función de Fortran que devuelve un entero con signo (Editar: una subrutina que escribe un valor entero de 32 o 64 bits con signo en una referencia entregada a una variable de 64 bits sin firmar en 64 sistemas de bits).

¿La implementación de referencia tiene una opinión sobre esto? Supongo que no hay ningún problema real, ya que un valor de size_t siempre podrá contener cualquier retorno no negativo de isamax (). Pero huele dudoso. (Editar: podría construir con índices de 64 bits en un sistema de 32 bits donde size_t es de 32 bits, ¿verdad? Luego se desborda. Además de la incomodidad de convertir size_t * a int * .)

Dado que las implementaciones optimizadas parecen haberse decidido por size_t allí, ¿debería la referencia aceptar ese hecho y seguirlo?

¿Y qué tan peligroso es, en realidad, vincular numpy con cblas de referencia?

OpenBLAS da el ejemplo. (...)
Dado que las implementaciones optimizadas parecen haberse decidido por size_t allí, ¿debería la referencia aceptar ese hecho y seguirlo?

Ciertamente no puedo hablar por numpy (o mkl, etc. para el caso), pero dudaría en afirmar que OpenBLAS es normativo en cualquier forma, y ​​menos en relación con lo que (creo) generalmente se ve como __la__ implementación de referencia. .

Seguro. Es solo que OpenBLAS o MKL son lo que la gente usa en la práctica y ambos parecen haberse decidido por

#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);

o similar

#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 la referencia

#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);

¿Cómo es que se apartan de la referencia aquí? ¿Hubo comunicación sobre eso? Además ... veo MKL y OpenBLAS definiendo una serie de funciones que ni siquiera son parte de CBLAS de referencia:

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);

Entonces, extender el estándar es una cosa, pero size_t vs int parece ser un problema serio en los sistemas de 64 bits. Esto debería resolverse de alguna manera. Me parece que el método Netlib es sensato: el mismo tipo que el que se usa para los índices. Como todos llaman a Fortran rutinas como esta al 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 una dirección de size_t para iamax, eso parece incorrecto. No encontré otra implementación que esta de referencia en las fuentes de OpenBLAS. ¿Son simplemente estúpidos cambiando el tipo externo de esa manera o estoy pasando por alto algo muy básico? ¿Alguien está usando estas funciones?

Hola a todos, Referencia BLAS, referencia CBLAS, referencia LAPACK, dos de los ejes principales de estos proyectos son (1) algoritmos numéricos y (2) definir interfaces comunes, una implementación de referencia y un conjunto de pruebas que va esto. Creo que todos los involucrados en estos proyectos están felices de ver y aprender de otros proyectos (OpenBLAS, MKL, etc.) sobre ingeniería de software, mejores prácticas para implementar el software, etc. Tenemos mucho que aprender de estos proyectos. (¡Y también aprendemos mucho de otros proyectos de álgebra lineal numérica!) De todos modos: BLAS de referencia, CBLAS, LAPACK pueden usar alguna mejora en su empaquetado CMake, interfaces, y si OpenBLAS (por ejemplo) tiene un mejor proceso, eso es adecuado para nosotros, bueno, estoy a favor de avanzar hacia este modelo.

Para agregar algo de contexto, el CBLAS nació de un comité (el Foro Técnico de Subprogramas de Álgebra Lineal Básica) que trabajó de 1996 a 2000 en la revisión del BLAS, como parte de esto definieron una interfaz C para el BLAS. Ver:
http://www.netlib.org/blas/blast-forum/
En particular, ver:
http://www.netlib.org/blas/blast-forum/cinterface.pdf
Creo que el CBLAS ofrecido por LAPACK es una implementación de la interfaz definida por el Foro Técnico de Subprogramas de Álgebra Lineal Básica hace 25 años.

Si hay sugerencias para mejorar CBLAS, envíelas. Puedo intentar transmitir esto a las distintas partes interesadas.

Gracias por la anotación. Entonces, la parte relevante parece ser B.2.2 en esa especificación, que dice que BLAS_INDEX generalmente es size_t , pero también podría elegirse para que sea idéntico al tipo de entero Fortran (firmado) utilizado para indexación. Depende de la implementación.

Entonces, parece que las implementaciones optimizadas populares eligieron size_t y la referencia de Netlib eligió el mismo número entero que usa para Fortran. Veo copias de cblas.h en varios proyectos que usan la biblioteca (como numpy, enviando un encabezado para una biblioteca externa), con esa línea

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

En https://github.com/LuaDist/gsl/blob/master/cblas/gsl_cblas.h , esto va acompañado de

/* 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 esto se originó en la implementación de referencia, pero ¿ha cambiado desde entonces? Mirando 41779680d1f233928b67f5f66c0b239aecb42774… veo que el conmutador CBLAS_INDEX con WeirdNEC ha estado allí antes de la compilación de 64 bits. Vaya, este compromiso es reciente. Ahora veo que size_t estaba en la referencia cblas.h hasta 2015, 83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 lo cambió e introdujo la definición de WeirdNEC. ¡No imaginé que esto fuera tan reciente! Extremadamente confuso.

También veo que la versión anterior de cblas.h entregó un int a la llamada de fortran, ahora CBLAS_INDEX . Esto parece ser correcto ahora, con un uso constante de CBLAS_INDEX como tipo de número entero y el cambio de 32 o 64 bits en la parte de Fortran.

Pero, ¿podría ser que las bibliotecas optimizadas que heredaron una versión anterior de cblas.h con size_t pero sincronizar fuentes con el código CBLAS actual de la referencia tengan un buen error? ¿No están haciendo algo como esto para el caso de 32 bits en un 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;
}

Esto resulta en

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

Inicializar el size_t a cero ayuda, pero probablemente solo en el caso de little-endian. ¿Nadie se mete en problemas por esto? Tengo que estar perdiendo algo.

Para concluir:

  1. El CBLAS de referencia tenía el size_t como valor de retorno primero.
  2. Sin embargo, usó int en la llamada real a Fortran.
  3. El flujo descendente (BLAS optimizado, usuarios de CBLAS) se ejecuta con la versión anterior del encabezado.
  4. Reference CBLAS presenta el truco WeirdNEC para un sistema específico, reemplazando size_t con int o long (¿coincidiendo con el lado de Fortran?!)
  5. La interfaz CBLAS de referencia de 64 bits se construye además de eso, usando CBLAS_INDEX todas partes para el entero predeterminado de Fortran.
  6. Los downstreams hicieron lo suyo con soporte de 64 bits, pero separándolo de CBLAS_INDEX , que siempre es size_t.
  7. Los downstreams heredan los envoltorios CBLAS que emplean CBLAS_INDEX para llamar a Fortran que espera un entero predeterminado.

Como resultado, esto suena como una rotura maravillosa. Los encabezados y el código divergieron. ¿Cómo es que nadie notó problemas todavía? ¿O me perdí la parte donde el código de envoltura CBLAS de referencia para isamax y amigos no se usa realmente?

OpenBLAS al menos no usa el código de envoltura CBLAS de Reference-LAPACK (y nunca lo hizo, la fuente está ahí pero no se compila)

@ martin-frbg Es bueno saberlo. ¿Puede señalar una ruta de código para, digamos, x86-64 que muestre cómo se pasa size_t al cálculo real de cblas_isamax ()? Encontré alguna implementación de kernel específica, pero no estoy seguro del caso general.

Sería bueno saber que nadie pasa un (size_t*) a la interfaz de Fortran.

Seguro que no es bueno que los proyectos simplemente asuman

size_t cblas_isamax(…)

cuando la biblioteca real podría ofrecer un int o long (o int64_t) como valor de retorno. Puede funcionar la mayor parte del tiempo con valores en registros de 64 bits, pero no es agradable. ¿Podemos rectificar esto en las implementaciones? La gente no se dio cuenta del ejemplo de Netlib en los últimos 5 años sobre el uso constante de CBLAS_INDEX .

el código relevante está en OpenBLAS / interface, por ejemplo, interface / imax.c se compila en cblas_isamax () cuando se define CBLAS, no hay código Fortran involucrado en su gráfico de llamadas.

Ah bueno. Entonces, el único caso que es realmente problemático es el de depender de proyectos que utilicen una copia de cblas.h que no se ajuste a la biblioteca.

No encuentro el uso real de cblas_isamax() y amigos en NumPy (y SciPy), por lo que esto podría ser solo un problema teórico. No obstante, debería arreglarse. Entonces:

  1. Otros siguen el ejemplo de Netlib de usar int32_t / int64_t (seamos explícitos mientras estamos en eso ;-) BLAS_INDEX tanto para los retornos de tamaño como para los argumentos de índice.
  2. Netlib cede y vuelve a size_t para esos retornos como otros.

¿Es este un tema aparte para discutir? Sin embargo, se relaciona con la elección de una biblioteca de 32 o 64 bits.

PD: Todavía no estoy seguro si las enumeraciones en la API son una buena idea (como tipo de datos real para argumentos de función y miembros de estructura), ya que hay opciones de compilador para cambiar qué entero se usa debajo de ellos. No es tan relevante en la práctica, pero de todos modos me incomoda.

Cuanto más pienso en esto, más me inclino hacia la Opción 2: tuvimos size_t en la API durante mucho tiempo. Luego Netlib cambió ese size_t a int o long. Independientemente de lo que coincida mejor con el código de Fortran o pueda ser más consistente, la API size_t fue establecida y la referencia de Netlib rompió eso.

¿Debo abrir un PR sobre cambiar las cosas 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);

¿de nuevo? Ya no debería haber macro en esta posición para enfatizar que siempre es size_t, en todas partes, pasado y futuro.

En https://github.com/numpy/numpy/issues/19243 ahora básicamente llegamos a: "Al diablo con Netlib, size_t funciona para todos los demás".

Hay tres razones para usar size_t :

  1. Todas las funciones de biblioteca estándar de C y C ++ aceptan y devuelven este valor, por ejemplo, void* malloc(size_t) , size_t strlen() o std::size_t std::vector<T>::size() (C ++). El uso de size_t evita truncar valores y conversiones firmadas / no firmadas.
  2. size_t se usa a menudo para expresar cantidades que no pueden ser negativas, por ejemplo, dimensiones de la matriz.
  3. Los estándares C y C ++ garantizan que puede almacenar el tamaño de cualquier matriz en un size_t y que puede indexar todos los elementos con size_t , cf. cppference.com: tamaño_t .

Editar: podría construir con índices de 64 bits en un sistema de 32 bits donde size_t es de 32 bits, ¿verdad? Entonces tienes desbordamiento.

No, porque un sistema de 32 bits puede tener más de 4 GB de memoria virtual (Linux admite esto), pero un solo proceso de 32 bits nunca puede acceder a más de 4 GB. Es decir, los 32 bits superiores de los índices de 64 bits nunca se utilizan.

_Límite de memoria para un proceso de 32 bits que se ejecuta en un sistema operativo Linux de 64 bits_

También estoy pensando que mantener size_t es lo correcto, porque cambiarlo fue la ruptura de ABI y desincronizó Netlib con el resto del mundo.

Pero me siento obligado a criticar tus argumentos ;-)

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

Cuando investigué esto, me topé con la admisión de que fue un error histórico usar un tipo sin firmar para índices de contenedor C ++, y probablemente incluso el tipo de retorno del método size (), ya que rápidamente termina mezclando números firmados y no firmados en de alguna manera. El estado actual de Netlib sería consistente consigo mismo, siempre usando tipos firmados para tamaño e índices, pero por supuesto inconsistente con malloc() , que tiene un requisito de tamaño sin firmar para poder abordar toda la memoria que cabe en 32 bits (o 64 bits, en teoría).

Me pregunto sobre eso en el código que escribí, donde finalmente entrego un índice como compensación a una llamada de función. El índice no está firmado, el desplazamiento está firmado. Aparte de que los compiladores (MSVC) se confunden con -unsigned_value , esto significaría que siempre tengo que preocuparme por un posible desbordamiento en la conversión.

Pero de todos modos, si se trata solo de calcular tamaños de memoria para entregar a malloc () y amigos, size_t es lo natural, y ha estado ahí antes en CBLAS.

Sobre posibles problemas con el estado actual del código, falta de coincidencia con cblas.h vendidos en las compilaciones:

No, porque un sistema de 32 bits puede tener más de 4 GB de memoria virtual (Linux admite esto), pero un solo proceso de 32 bits nunca puede acceder a más de 4 GB. Es decir, los 32 bits superiores de los índices de 64 bits nunca se utilizan.

Bien, size_t queda en 32 bits. Cuando (por tonto que sea) construiste cblas_isamax() para devolver un entero de 64 bits, después de hackear la compilación para no usar long , pero int64_t , por supuesto, ¿qué realmente sucede en tal uso?

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

La convención de llamadas x86 podría poner el valor de 64 bits en EAX y EDX. O podría funcionar con un retorno de puntero y algo de búfer. Pero, ¿qué harían otras arquitecturas? Por lo tanto, es posible que no obtenga corrupción, pero seguramente un valor incorrecto. El mejor caso es que se ignoren los 32 bits más altos.

Ahora imagina un sistema big-endian de 32 bits (alguna forma de ARM) ... ¿seguro que incluso obtendrás la mitad deseada del valor devuelto?

Realmente no podría trabajar con datos no dispersos que necesiten índices de 64 bits en el programa de 32 bits, claro. Pero el simple hecho de poder realizar una llamada a una función que no coincida y que _ al menos_ dé resultados incorrectos parece poco saludable.

Hice algunas pruebas rápidas ... en Linux x86 ( gcc -m32 en un sistema x86-64), simplemente elimine los 32 bits superiores.

El caso más interesante… 64 bit size_t:

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

Nuevamente, en x86-64, la relación peculiar entre RAX de 64 bits y EAX de 32 bits hace que las cosas estén algo bien definidas para que también ponga a cero silenciosamente los 32 bits superiores una vez que realice una operación de 32 bits en el registro compartido. Pero es divertido tener una definición de función un poco extraña:

$ 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

Se podría argumentar si es inteligente que el compilador trabaje en el registro completo de 64 bits y deje los 32 bits superiores sin borrar para una función que se espera que devuelva un valor de 32 bits, pero es perfectamente legal si confía solo en el llamador. utilizando los 32 bits inferiores, supongo.

$ 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

Divertida. Un valor de retorno de 32 bits que da más de lo que es posible en 32 bits. Esto es lo que puede suceder (en principio) con el estado actual de Netlib CBLAS vinculado con el código que espera size_t. Sin embargo, supongo que los 32 bits superiores de RAX serán cero en el código real en la práctica. Pero quién sabe ... el compilador espera que la persona que llama no use más de los 32 bits inferiores en cualquier plataforma ... también podría almacenar basura allí.

Entonces ... ¿estamos de acuerdo en volver a mover Netlib a size_t como valor de retorno?

¡Gracias por todos estos valiosos comentarios!

Discutí este tema un poco con @langou. Basado en la discusión aquí, mi propuesta es:

En un PR separado:

  1. Volvemos a cblas.h que usa dos definiciones enteras, digamos CBLAS_INDEX y CBLAS_INT. Eso es lo que sucede en MKL (CBLAS_INDEX y MKL_INT) y OpenBLAS (CBLAS_INDEX y blasint). CBLAS_INDEX se utilizará solo en la devolución de i*amax . Con eso, restauramos un ABI que sea compatible con otros BLAS.
  2. Además, elegimos el valor predeterminado de CBLAS_INDEX como size_t y recopilamos opiniones de la comunidad.

Creo que esta idea está alineada (o quizás la misma) detrás de las discusiones recientes en este hilo.
Como señaló @drhpc ,
https://github.com/Reference-LAPACK/lapack/commit/83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 cambió el valor predeterminado de CBLAS_INDEX y
https://github.com/Reference-LAPACK/lapack/commit/41779680d1f233928b67f5f66c0b239aecb42774 cambió el uso de CBLAS_INDEX.

Solo para reforzar:

  • OpenBLAS, MKL, GNU Scientific Library y Numpy usan size_t de forma predeterminada.
  • La interfaz C para BLAS (https://www.netlib.org/blas/blast-forum/cinterface.pdf) indica que, normalmente, CBLAS_INDEX = size_t .

¿Estás de acuerdo? Si lo hace, puedo abrir el PR. O tal vez a @drhpc le gustaría hacer eso.

Estoy de acuerdo. Y, por favor, siga adelante con las relaciones públicas.

@ mgates3 me mencionó la discusión sobre el Grupo de Google Slate:
https://groups.google.com/a/icl.utk.edu/g/slate-user/c/f5y6gt0aoLs/m/oQyyhikwCgAJ
La discusión no se trata de lo que debería ser "CBLAS_INDEX", sino más bien de lo que debería ser "CBLAS_INT". ¿CBLAS_INT debería ser size_t o entero con signo o etc.? Creo que los participantes están haciendo buenos puntos, así que estoy de acuerdo.

Por favor, vea el n. ° 588.

Bien, size_t permanece en 32 bits. Cuando (por tonto que sea) construiste cblas_isamax() para devolver un entero de 64 bits, después de piratear la compilación para no usar long , pero int64_t , por supuesto, ¿qué realmente sucede en tal uso?

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

La convención de llamadas x86 podría poner el valor de 64 bits en EAX y EDX. O podría funcionar con un retorno de puntero y algo de búfer. Pero, ¿qué harían otras arquitecturas? Por lo tanto, es posible que no obtenga corrupción, pero seguramente un valor incorrecto. El mejor caso es que se ignoren los 32 bits más altos.

Ahora imagina un sistema big-endian de 32 bits (alguna forma de ARM) ... ¿seguro que incluso obtendrás la mitad deseada del valor devuelto?

Se acabó el juego. En las CPU de armado de 32 bits, se pueden pasar y devolver cuatro valores de 32 bits en registros, los valores de 64 bits ocupan dos registros consecutivos, consulte la Sección 6.1.1.1 en _Estándar de llamada de procedimiento para la arquitectura de armado_ . En lugar de escribir en un registro, el destinatario de la llamada saturará dos registros con sus enteros de 64 bits; obviamente esto es un problema. Tan pronto como la persona que llama se queda sin registros de parámetros, se utiliza la pila. La alineación de la pila es de 32 bits, pero en lugar de leer o escribir 32 bits, el destinatario de la llamada escribe 64 bits; de nuevo, esto ha terminado y este problema (falta de coincidencia de los tamaños de lectura / escritura de la pila) debería causar problemas en todas las arquitecturas de conjuntos de instrucciones en algún momento.

Me pregunto sobre eso en el código que escribí, donde finalmente entrego un índice como compensación a una llamada de función. El índice no está firmado, el desplazamiento está firmado. Aparte de que los compiladores (MSVC) se confunden con -unsigned_value, esto significaría que siempre tengo que preocuparme por un posible desbordamiento en la conversión.

No, los comités estándar detrás de C y C ++ hacen que su código se comporte de la manera obvia en este caso: Si u es un valor sin firmar y s es un valor con signo, donde u tiene al menos tantos bits como s , entonces u + s producirá el resultado matemáticamente correcto a menos que u + s desborde o subdesborde. Si es insuficiente / desbordante, el resultado se ajustará, es decir, (u + s) mod 2^b , donde b es el número de bits en u y s . Por otro lado, si el tipo firmado puede representar todos los valores del tipo sin firmar, el valor sin firmar se convertirá al tipo sin firmar.

Las cláusulas relevantes del borrador estándar C11 son las siguientes:

  • 6.2.5.9: Las operaciones binarias con solo operandos sin signo no pueden desbordarse; el resultado se toma como módulo MAX + 1 , donde MAX es el valor representable más grande.
  • 6.3.1.3: Dado un valor con signo s , se convierte al valor sin firmar s si s >= 0 ; de lo contrario, se convierte en s + MAX + 1 .
  • 6.3.1.8: Los operandos con y sin signo [del mismo tamaño] se convierten a sin signo; un operando sin signo se convierte en un tipo con signo si el tipo con signo puede representar todos los valores del tipo sin signo

Por lo tanto, u + s (sintaxis C) se evaluará para

  • (u + s) mod (M + 1) si s >= 0 ,
  • (u + s + M + 1) mod (M + 1) contrario.

En ausencia de desbordamiento o subdesbordamiento, esta expresión se evaluará en u + s que es el resultado deseado intuitivamente.

Cuando investigué esto, me topé con la admisión de que fue un error histórico usar un tipo sin firmar para índices de contenedor C ++, y probablemente incluso el tipo de retorno del método size (), ya que rápidamente termina mezclando números firmados y no firmados en de alguna manera.

Hay algunos programadores de C ++ (incluido el inventor de C ++) que proponen usar enteros con signo en todas partes, consulte las Pautas básicas de C ++, pero yo no llamaría a esto una admisión. El problema con la política de "enteros firmados en todas partes" es

  • comprobación de valores mínimos: con un entero sin signo en muchos casos es superfluo comprobar el valor mínimo, con un entero con signo es obligatorio; esto es propenso a errores y puede causar problemas de seguridad; consulte, por ejemplo, CWE-839 _Comparación de rango numérico sin verificación mínima_ .
  • desbordamientos: un desbordamiento sin firmar tiene un resultado bien definido, mientras que un desbordamiento de entero con signo constituye un comportamiento indefinido.

Puede intentar comprobar si hay un desbordamiento firmado con la expresión a + b < a pero el compilador podría optimizarlo sin previo aviso, ver por ejemplo el error GCC 30475 _assert (int + 100> int) optimizado lejos_ de 2007. Esto funcionaría aunque con enteros sin signo ( a sin firmar, b posiblemente firmado y b con como máximo tantos bits como a ). Al ver el artículo _OptOut - Compiler Undefined Behavior Optimizations_ de 2020, el comportamiento de GCC aparentemente no ha cambiado.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

weslleyspereira picture weslleyspereira  ·  5Comentarios

JHenneberg picture JHenneberg  ·  10Comentarios

pablosanjose picture pablosanjose  ·  41Comentarios

Peter9606 picture Peter9606  ·  7Comentarios

christoph-conrads picture christoph-conrads  ·  26Comentarios