¿Se le permite al compilador doblar constantemente un volátil local?

25

Considere este código simple:

void g();

void foo()
{
    volatile bool x = false;
    if (x)
        g();
}

https://godbolt.org/z/I2kBY7

Puede ver que gccni clangoptimizar la llamada potencial a g. En mi opinión, esto es correcto: la máquina abstracta debe suponer que las volatilevariables pueden cambiar en cualquier momento (debido, por ejemplo, a un mapeo de hardware), por lo que el plegado constante de la falseinicialización en la ifcomprobación sería incorrecto.

Pero MSVC elimina la llamada por gcompleto ( volatile¡aunque mantiene las lecturas y escrituras en el !). ¿Es este comportamiento compatible con el estándar?


Antecedentes: ocasionalmente uso este tipo de construcción para poder activar / desactivar la salida de depuración sobre la marcha: el compilador siempre debe leer el valor de la memoria, por lo que cambiar esa variable / memoria durante la depuración debería modificar el flujo de control en consecuencia . La salida de MSVC vuelve a leer el valor, pero lo ignora (presumiblemente debido a la eliminación constante del código y / o eliminación del código muerto), lo que, por supuesto, derrota mis intenciones aquí.


Ediciones:

  • volatileAquí se analiza la eliminación de las lecturas y escrituras : ¿está permitido que un compilador optimice una variable volátil local? (¡Gracias Nathan!). Creo que el estándar es muy claro que esas lecturas y escrituras deben suceder. Pero esa discusión no cubre si es legal que el compilador tome los resultados de esas lecturas por sentado y optimice en función de eso. Supongo que esto está sub / no especificado en el estándar, pero sería feliz si alguien demostrara que estoy equivocado.

  • Por supuesto, puedo hacer xuna variable no local para evitar el problema. Esta pregunta es más por curiosidad.

Max Langhof
fuente
3
Esto me parece un error obvio del compilador.
Sam Varshavchik
1
Hasta donde yo sé, esto es legal bajo la regla de como si. El compilador puede demostrar que, aunque el objeto es volátil, no hay forma de modificar su estado para que pueda desplegarse. No tengo la confianza suficiente para poner eso en una respuesta, pero siento que es correcto.
NathanOliver
3
Pero creo que el argumento del OP de que la variable puede ser modificada por un depurador es razonable. Tal vez alguien debería presentar un informe de error con MSVC.
Brian
2
@curiousguy Incluso si descarta el resultado y / o asume el valor exacto, aún lo ha leído.
Deduplicador
2
Curiosamente, solo lo hace para x64. La versión x86 todavía llama a g () godbolt.org/z/nc3Y-f
Jerry Jeremiah

Respuestas:

2

Creo que [intro.execution] (el número de párrafo varía) podría usarse para explicar el comportamiento de MSVC:

Una instancia de cada objeto con duración de almacenamiento automática está asociada con cada entrada en su bloque. Tal objeto existe y retiene su último valor almacenado durante la ejecución del bloque y mientras el bloque está suspendido ...

El estándar no permite la eliminación de una lectura a través de un valor de gl volátil, pero el párrafo anterior podría interpretarse como que permite predecir el valor false.


Por cierto, el estándar C (N1570 6.2.4 / 2) dice que

Existe un objeto, tiene una dirección constante y conserva su último valor almacenado durante toda su vida útil. 34


34) En el caso de un objeto volátil, la última tienda no necesita ser explícita en el programa.

No está claro si podría haber un almacenamiento no explícito en un objeto con duración de almacenamiento automático en la memoria C / modelo de objeto.

Abogado de idiomas
fuente
De acuerdo en que el compilador puede saber cuándo son posibles almacenes no explícitos en la plataforma de destino
MM
1
Entonces, si esto es cierto, ¿entonces los objetos volátiles locales son (al menos en MSVC) completamente inútiles? ¿Hay algo que agregar le volatilecompre (aparte de lecturas / escrituras superfluas) si se ignora para fines de optimización?
Max Langhof
1
@MaxLanghof Hay una diferencia entre completamente inútil y no tener el efecto que deseas / esperas.
Deduplicador el
1
@MaxLanghof Los resultados intermedios de los cálculos de coma flotante a veces se promocionan a un registro de 80 bits que causa problemas de precisión. Creo que esto es un gcc-ismo y se evita declarando todos los dobles como volátiles. Ver: gcc.gnu.org/bugzilla/show_bug.cgi?id=323
ForeverLearning el
1
@philipxy PS Vea mi respuesta Sé que el acceso está definido por la implementación. La pregunta no es sobre el acceso (se accede al objeto), sino la predicción del valor.
Language Lawyer
2

TL; DR El compilador puede hacer lo que quiera en cada acceso volátil. Pero la documentación tiene que decirle: "La semántica de un acceso a través de un valor de volátil está definida por la implementación".


El estándar define para un programa secuencias permitidas de "accesos volátiles" y otros "comportamientos observables" (logrados mediante "efectos secundarios") que una implementación debe respetar según "la regla" como si ".

Pero el estándar dice (mi énfasis en negrita):

Borrador de trabajo, estándar para lenguaje de programación C ++
Número de documento: N4659
Fecha: 2017-03-21

§ 10.1.7.1 Los calificadores cv

5 La semántica de un acceso a través de un valor de gl volátil está definida por la implementación. [...]

Del mismo modo para dispositivos interactivos (mi énfasis en negrita):

§ 4.6 Ejecución del programa

5 Una implementación conforme que ejecute un programa bien formado producirá el mismo comportamiento observable que una de las posibles ejecuciones de la instancia correspondiente de la máquina abstracta con el mismo programa y la misma entrada. [...]

7 Los requisitos mínimos en una implementación conforme son:

(7.1) - Los accesos a través de valores volátiles se evalúan estrictamente de acuerdo con las reglas de la máquina abstracta.
(7.2) - Al finalizar el programa, todos los datos escritos en los archivos serán idénticos a uno de los posibles resultados que la ejecución del programa de acuerdo con la semántica abstracta habría producido.
(7.3) - La dinámica de entrada y salida de los dispositivos interactivos se llevará a cabo de tal manera que la salida de solicitud se entregue antes de que un programa espere la entrada. Lo que constituye un dispositivo interactivo está definido por la implementación.

Estos colectivamente se conocen como el comportamiento observable del programa. [...]

(De todos modos, el código específico que se genera para un programa no está especificado por el estándar).

Entonces, aunque el estándar dice que los accesos volátiles no se pueden eludir de las secuencias abstractas de los efectos secundarios abstractos de la máquina y los comportamientos observables consecuentes que algunos códigos (tal vez) definen, no puede esperar que nada se refleje en el código objeto o en el mundo real comportamiento a menos que la documentación del compilador le diga qué constituye un acceso volátil . Lo mismo para dispositivos interactivos.

Si está interesado en la volatilidad frente a las secuencias abstractas de los efectos secundarios abstractos de la máquina y / o los comportamientos observables consiguientes que algunos códigos (tal vez) definen , dígalo . Pero si está interesado en qué código objeto correspondiente se genera , debe interpretarlo en el contexto de su compilador y compilación .

Crónicamente, las personas creen erróneamente que para los accesos volátiles, una evaluación / lectura de máquina abstracta provoca una lectura implementada y una asignación / escritura de máquina abstracta provoca una escritura implementada. No hay ninguna base para esta creencia en ausencia de documentación de implementación que lo diga Cuando / si la implementación dice que en realidad hace algo en un "acceso volátil", la gente está justificada en esperar que algo, tal vez, la generación de cierto código objeto.

filipina
fuente
1
Quiero decir, esto se reduce a "si se me ocurre una máquina donde todos los efectos secundarios mencionados son no operativos, entonces tengo una implementación legal de C ++ compilando cada programa en un no operativo" . Por supuesto, estamos interesados ​​en efectos prácticamente observables, ya que los efectos secundarios abstractos de la máquina son tautológicamente abstractos. Esto requiere algún concepto básico de efectos secundarios, y razonaría que "los accesos volátiles conducen a instrucciones explícitas de acceso a la memoria" es parte de eso (incluso si al estándar no le importa), por lo que realmente no compro el "decir si quieres código en lugar de semántica abstracta ". Aún así, +1.
Max Langhof
Sí, la calidad de la implementación es relevante. Sin embargo, aparte de las escrituras en archivos, los "observables" [sic] están definidos por la implementación. Si desea poder establecer puntos de interrupción, acceder a cierta memoria real, ignorar "volátil", etc. en lecturas y escrituras volátiles abstractas, entonces debe hacer que su escritor compilador genere el código apropiado . PS En C ++, "efecto secundario" no se usa para el comportamiento observabke per se, se usa para describir la evaluación de orden parcial de subexpresiones.
philipxy
¿Te importaría explicar el [sic]? ¿Qué fuente estás citando y cuál es el error?
Max Langhof
En resumen, quiero decir que una máquina abstracta observable solo es observable en el mundo real si y cómo la implementación lo dice.
philipxy
1
¿Está diciendo que una implementación podría afirmar que no existe un dispositivo interactivo, por lo que cualquier programa puede hacer cualquier cosa, y aún así sería correcto? (Nota: no entiendo su énfasis en los dispositivos interactivos)
Curioso
-1

Creo que es legal omitir el cheque.

El párrafo que a todos les gusta citar

34) En el caso de un objeto volátil, la última tienda no necesita ser explícita en el programa

no implica que una implementación deba asumir que tales almacenes son posibles en cualquier momento o para cualquier variable volátil. Una implementación sabe qué tiendas son posibles. Por ejemplo, es completamente razonable suponer que tales escrituras implícitas solo ocurren para variables volátiles que se asignan a registros de dispositivos, y que dicha asignación solo es posible para variables con enlace externo. O una implementación puede suponer que tales escrituras solo se transfieren a ubicaciones de memoria alineadas con el tamaño de las palabras.

Dicho esto, creo que el comportamiento de MSVC es un error. No hay ninguna razón en el mundo real para optimizar la llamada. Tal optimización puede ser compatible, pero es innecesariamente malvada.

norte. 'pronombres' m.
fuente
¿Puedes explicar por qué es malo? En el código muestra, la función literalmente nunca se puede llamar.
David Schwartz
@DavidSchwartz Solo puede concluir que después de especificar la semántica de las variables volátiles locales (vea el párrafo citado arriba). El estándar en sí mismo señala que volatilese supone que es una pista para la implementación de que el valor puede cambiar por medios desconocidos para la implementación.
Max Langhof
@MaxLanghof No hay forma de que la implementación pueda manejar correctamente algo desconocido para ella. Lo que realmente hacen las plataformas útiles es especificar para qué se puede y qué no se puede usar volatileen esa plataforma y fuera de esa especificación, siempre será un juego de dados.
David Schwartz
@DavidSchwartz Por supuesto que puede, siguiendo la semántica (en particular, las lecturas y escrituras) de la máquina abstracta. Es posible que no pueda optimizar correctamente, ese es el punto del estándar. Ahora, es una nota y, por lo tanto, no es normativa, y como ambos dijimos, la implementación puede especificar qué volatilehace y apegarse a eso. Mi punto es que el código en sí (de acuerdo con la máquina estándar / abstracta de C ++) no le permite determinar si gse puede llamar.
Max Langhof
@DavidSchwartz El código no muestra nada de eso, porque la ausencia de escrituras implícitas no se sigue del código.
n. 'pronombres' m.