É permitido ao compilador dobrar constantemente um volátil local?

25

Considere este código simples:

void g();

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

https://godbolt.org/z/I2kBY7

Você pode ver que gccnem clangotimiza a chamada potencial para g. Isso está correto no meu entendimento: A máquina abstrata deve assumir que as volatilevariáveis ​​podem mudar a qualquer momento (devido a serem, por exemplo, mapeadas por hardware), portanto, dobrar constantemente a falseinicialização na ifverificação estaria errado.

Mas o MSVC elimina gtotalmente a chamada (mantendo as leituras e gravações no volatileentanto!). Esse comportamento é compatível com os padrões?


Antecedentes: Ocasionalmente uso esse tipo de construção para ativar / desativar a saída de depuração on-the-fly: O compilador sempre deve ler o valor da memória, portanto, alterar a variável / memória durante a depuração deve modificar o fluxo de controle adequadamente . A saída MSVC relê o valor, mas o ignora (provavelmente devido à constante dobragem e / ou eliminação do código morto), o que obviamente derrota minhas intenções aqui.


Editar% s:

  • A eliminação das leituras e gravações volatileé discutida aqui: É permitido que um compilador otimize uma variável volátil local? (obrigado Nathan!). Eu acho que o padrão é bastante claro de que essas leituras e gravações devem acontecer. Mas essa discussão não cobre se é legal para o compilador considerar os resultados dessas leituras como garantidos e otimizar com base nisso. Suponho que isso seja sub / não especificado no padrão, mas ficaria feliz se alguém me provasse errado.

  • É claro que posso criar xuma variável não local para contornar o problema. Esta questão é mais por curiosidade.

Max Langhof
fonte
3
Isso parece um bug óbvio do compilador para mim.
Sam Varshavchik
11
Tanto quanto eu sei, isso é legal sob a regra como se. O compilador pode provar que, embora o objeto seja volátil, não há como o estado ser modificado para que possa ser desdobrado. Não estou confiante o suficiente para responder isso, mas acho que está correto.
NathanOliver 16/10/19
3
Mas acho que o argumento do OP de que a variável pode ser modificada por um depurador é razoável. Talvez alguém deva registrar um relatório de erro na MSVC.
Brian
2
@curiousguy Mesmo se você descartar o resultado e / ou assumir o valor exato, ainda o leu.
Deduplicator
2
Curiosamente, isso só é possível para x64. A versão x86 ainda chama g () godbolt.org/z/nc3Y-f
Jerry Jeremiah

Respostas:

2

Eu acho que [intro.execution] (número do parágrafo varia) poderia ser usado para explicar o comportamento da MSVC:

Uma instância de cada objeto com duração de armazenamento automático é associada a cada entrada em seu bloco. Esse objeto existe e retém seu último valor armazenado durante a execução do bloco e enquanto o bloco está suspenso ...

O padrão não permite a eliminação de uma leitura através de um valor de volume volátil, mas o parágrafo acima pode ser interpretado como permitindo prever o valor false.


BTW, o Padrão C (N1570 6.2.4 / 2) afirma que

Um objeto existe, tem um endereço constante e mantém seu último valor armazenado ao longo de sua vida útil. 34


34) No caso de um objeto volátil, o último armazenamento não precisa ser explícito no programa.

Não está claro se poderia haver um armazenamento não explícito em um objeto com duração de armazenamento automático na memória C / modelo de objeto.

Advogado de idiomas
fonte
Concorde que o compilador pode saber quando são possíveis armazenamentos não explícitos na plataforma de destino.
MM
11
Então, se isso for verdade, os objetos voláteis locais são (pelo menos no MSVC) totalmente inúteis? Existe algo que a adição volatilelhe compre (exceto leituras / gravações supérfluas) se for ignorada para fins de otimização?
quer
11
@ MaxLanghof Há uma diferença entre totalmente inútil e não ter o efeito que você deseja / espera.
Deduplicator
11
@MaxLanghof Os resultados intermediários dos cálculos de ponto flutuante às vezes são promovidos para um registro de 80 bits, causando problemas de precisão. Eu acredito que este é um gcc-ism e é evitado declarando todas essas dobras como voláteis. Veja: gcc.gnu.org/bugzilla/show_bug.cgi?id=323
ForeverLearning
11
@ philipxy PS Veja minha resposta Eu sei que o acesso é definido pela implementação. A questão não é sobre acesso (o objeto é acessado), mas a previsão do valor.
Language Lawyer
2

TL; DR O compilador pode fazer o que quiser em cada acesso volátil. Mas a documentação deve lhe informar .-- "A semântica de um acesso através de um valor volátil volátil é definida pela implementação".


O padrão define para um programa sequências permitidas de "acessos voláteis" e outros "comportamentos observáveis" (alcançados através de "efeitos colaterais") que uma implementação deve respeitar de acordo com "a regra 'como se'".

Mas o padrão diz (minha ênfase em negrito):

Rascunho de trabalho, padrão para linguagem de programação C ++
Número do documento: N4659
Data: 2017-03-21

§ 10.1.7.1 Os qualificadores cv

5 A semântica de um acesso através de um valor de volume volátil é definida pela implementação. [...]

Da mesma forma para dispositivos interativos (ênfase em negrito):

§ 4.6 Execução do programa

5 Uma implementação em conformidade executando um programa bem formado deve produzir o mesmo comportamento observável que uma das possíveis execuções da instância correspondente da máquina abstrata com o mesmo programa e a mesma entrada. [...]

7 Os requisitos mínimos em uma implementação em conformidade são:

(7.1) - Os acessos através de valores voláteis são avaliados estritamente de acordo com as regras da máquina abstrata.
(7.2) - No término do programa, todos os dados gravados em arquivos devem ser idênticos a um dos resultados possíveis que a execução do programa, de acordo com a semântica abstrata, teria produzido.
(7.3) - A dinâmica de entrada e saída de dispositivos interativos deve ocorrer de maneira que a saída solicitada seja realmente entregue antes que um programa aguarde a entrada. O que constitui um dispositivo interativo é definido pela implementação.

Estes são chamados coletivamente de comportamento observável do programa. [...]

(De qualquer forma, qual código específico é gerado para um programa não é especificado pelo padrão.)

Portanto, embora o padrão diga que acessos voláteis não podem ser eliminados das seqüências abstratas dos efeitos colaterais da máquina abstrata e consequentes comportamentos observáveis ​​que algum código (talvez) define, você não pode esperar que nada seja refletido no código de objeto ou no mundo real comportamento, a menos que a documentação do compilador informe o que constitui um acesso volátil . O mesmo vale para dispositivos interativos.

Se você está interessado em volatilidade em relação às seqüências abstratas dos efeitos colaterais da máquina abstrata e / ou comportamentos observáveis ​​consequentes que algum código (talvez) define, então o diga . Mas se você estiver interessado em qual código de objeto correspondente é gerado , deve interpretá-lo no contexto de seu compilador e compilação .

Cronicamente, as pessoas acreditam erroneamente que, para acessos voláteis, uma avaliação / leitura abstrata da máquina causa uma leitura implementada e uma atribuição / gravação abstrata da máquina causa uma gravação implementada. Não há base para essa crença na documentação de implementação ausente dizendo isso. Quando / se a implementação diz que realmente faz algo com um "acesso volátil", as pessoas são justificadas em esperar que algo - talvez, a geração de determinado código de objeto.

philipxy
fonte
11
Quero dizer, isso se resume a "se eu criar uma máquina em que todos os efeitos colaterais mencionados não funcionem, então eu tenho uma implementação legal em C ++ compilando todos os programas em um não operacional" . É claro que estamos interessados ​​em efeitos praticamente observáveis, pois os efeitos colaterais abstratos da máquina são tautologicamente abstratos. Isso requer algum conceito básico de efeitos colaterais, e eu argumentaria que "acessos voláteis levam a instruções explícitas de acesso à memória" faz parte disso (mesmo que o padrão não se importe), então eu realmente não comprei o "say se você quiser código em vez de semântica abstrata ". Ainda assim, +1.
precisa
Sim, a qualidade da implementação é relevante. No entanto, além das gravações em arquivos, "observáveis" [sic] são definidos pela implementação. Se você deseja definir pontos de interrupção, acessar determinadas memórias reais, ter 'volátil' ignorado etc. em leituras e gravações voláteis abstratas, é necessário que o escritor do compilador produza o código apropriado . PS No C ++, "efeito colateral" não é usado para o comportamento de observação em si, é usado para descrever a avaliação de ordem parcial de subexpressões.
philipxy
Gostaria de explicar o [sic]? Qual fonte você está citando e qual é o erro?
Max Langhof
Resic Apenas quero dizer que uma máquina abstrata observável só é observável no mundo real se e como a implementação diz que é.
philipxy
11
Você está dizendo que uma implementação poderia alegar que não existe um dispositivo interativo, para que qualquer programa possa fazer qualquer coisa e ainda assim esteja correto? (Nota: Eu não entendo a sua ênfase em dispositivos interativos.)
curiousguy
-1

Eu acredito que é legal pular o cheque.

O parágrafo que todo mundo gosta de citar

34) No caso de um objeto volátil, o último armazenamento não precisa ser explícito no programa

não implica que uma implementação deva assumir que tais armazenamentos são possíveis a qualquer momento ou para qualquer variável volátil. Uma implementação sabe quais lojas são possíveis. Por exemplo, é inteiramente razoável supor que tais gravações implícitas só acontecem para variáveis ​​voláteis mapeadas para registros de dispositivos e que esse mapeamento só é possível para variáveis ​​com ligação externa. Ou uma implementação pode assumir que tais gravações só ocorrem em locais de memória alinhados e com tamanho de palavra.

Dito isto, acho que o comportamento do MSVC é um bug. Não há motivo do mundo real para otimizar a chamada. Essa otimização pode ser compatível, mas é desnecessariamente má.

n. 'pronomes' m.
fonte
Você pode explicar por que é mau? No código mostra, a função literalmente nunca pode ser chamada.
David Schwartz
@DavidSchwartz Você só pode concluir que depois de especificar a semântica das variáveis ​​voláteis locais (consulte o parágrafo citado acima). O próprio padrão observa que volatileé suposto ser uma dica para a implementação que o valor pode mudar por meios desconhecidos para a implementação.
Max Langhof
@ MaxLanghof Não há como a implementação lidar corretamente com algo desconhecido. O que as plataformas úteis realmente fazem é especificar o que você pode e não pode usar volatilenessa plataforma e fora dessa especificação, sempre será uma porcaria.
David Schwartz
@DavidSchwartz É claro que pode - seguindo a semântica (em particular, as leituras e gravações) da máquina abstrata. Talvez não seja possível otimizar corretamente - esse é o objetivo do padrão. Agora, é uma nota e, portanto, não é normativa, e, como dissemos, a implementação pode especificar o que volatilefaz e manter isso. O que quero dizer é que o próprio código (de acordo com a máquina padrão / abstrata C ++) não permite que você determine se gpode ser chamado.
precisa
@DavidSchwartz O código não mostra nada parecido, porque a ausência de gravações implícitas não segue o código.
n. 'pronomes' m.