Partilhar via


Suprimir a emissão do sinalizador localsinit.

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).

Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .

Resumo

Permitir suprimir a emissão do indicador localsinit através do atributo SkipLocalsInitAttribute.

Motivação

Contexto geral

Por especificação CLR, as variáveis locais que não contêm referências não são inicializadas com um valor específico pela VM/JIT. A leitura de tais variáveis sem inicialização é segura para o tipo, mas, caso contrário, o comportamento é indefinido e específico da implementação. Normalmente, as variáveis locais não inicializadas contêm valores residuais na memória que agora está ocupada pela estrutura da pilha. Isso pode levar a um comportamento não determinista e a bugs difíceis de reproduzir.

Há duas maneiras de "atribuir" uma variável local:

  • armazenando um valor ou
  • especificando o sinalizador localsinit, que força tudo o que é alocado do pool de memória local, a ser inicializados a zero. Nota: isso inclui tanto variáveis locais quanto dados stackalloc.

O uso de dados não inicializados é desencorajado e não é permitido em código verificável. Embora possa ser possível provar isso por meio da análise de fluxo, é permitido que o algoritmo de verificação seja conservador e simplesmente exija que localsinit seja definido.

Historicamente, o compilador C# emite o sinalizador localsinit em todos os métodos que declaram locais.

Embora o C# empregue a análise de atribuição definitiva que é mais rigorosa do que a especificação CLR exigiria (o C# também precisa considerar o escopo dos locais), não é estritamente garantido que o código resultante seria formalmente verificável:

  • As regras CLR e C# podem não concordar se passar um argumento local como out é uma use.
  • As regras CLR e C# podem não concordar com o tratamento de ramificações condicionais quando as condições são conhecidas (propagação constante).
  • O CLR poderia muito bem simplesmente exigir localinits, uma vez que isso é permitido.

Problema

Em aplicativos de alto desempenho, o custo da inicialização zero forçada pode ser percetível. É particularmente percetível quando stackalloc é usado.

Em alguns casos, o JIT pode evitar a inicialização a zero de locais individuais quando essa inicialização é eliminada por atribuições subsequentes. Nem todas as JITs fazem isso e essa otimização tem limites. Não ajuda com stackalloc.

Para ilustrar que o problema é real - existe um bug conhecido onde um método que não contém nenhuma variável local IL não teria o indicador localsinit. O bug já está sendo explorado pelos usuários, colocando stackalloc em tais métodos - intencionalmente para evitar custos de inicialização. Apesar de a ausência de variáveis locais IL ser uma métrica instável e poder variar dependendo das mudanças na estratégia de geração de código. O bug deve ser corrigido e os usuários devem obter uma maneira mais documentada e confiável de suprimir o sinalizador.

Projeto detalhado

Permita especificar System.Runtime.CompilerServices.SkipLocalsInitAttribute para indicar ao compilador que não emita o sinalizador localsinit.

O resultado final disso será que as variáveis locais podem não ser inicializadas a zero pelo JIT, o que na maioria dos casos não é observável em C#.
Além disso, os dados stackalloc não serão inicializados com zero. Isso é definitivamente observável, mas também é o cenário mais motivador.

Os alvos de atributos permitidos e reconhecidos são: Method, Property, Module, Class, Struct, Interface, Constructor. No entanto, o compilador não exigirá que o atributo seja definido com os destinos listados nem se importará com qual assembly o atributo está definido.

Quando o atributo é especificado em um contêiner (class, module, contendo método para um método aninhado, ...), o sinalizador afeta todos os métodos contidos no contêiner.

Os métodos sintetizados "herdam" o sinalizador do contêiner/proprietário lógico.

O indicador afeta apenas a estratégia de geração de código para corpos de método reais. Ou seja, a bandeira não tem efeito sobre métodos abstratos e não é propagada para métodos de substituição/implementação.

Este é explicitamente um recurso de compilador e não um recurso de linguagem.
Da mesma forma que as opções de linha de comando do compilador, o recurso controla os detalhes de implementação de uma estratégia específica de codegen e não precisa ser exigido pela especificação C#.

Desvantagens

  • Compiladores antigos/outros podem não honrar o atributo. Ignorar o atributo é um comportamento compatível. Pode apenas resultar em um ligeiro impacto de desempenho.

  • O código sem o sinalizador localinits pode desencadear falhas de verificação. Os usuários que pedem esse recurso geralmente não estão preocupados com a verificabilidade.

  • A aplicação do atributo em níveis mais altos do que um método individual tem efeito não local, que é observável quando stackalloc é usado. No entanto, este é o cenário mais solicitado.

Alternativas

  • omitir o sinalizador localinits quando o método é declarado no contexto unsafe. Isso poderia causar uma mudança de comportamento silenciosa e perigosa de determinista para não determinista em um caso de stackalloc.

  • omitir sempre o indicador localinits. Ainda pior do que acima.

  • Omitir o sinalizador localinits, a menos que se use stackalloc no corpo do método. Não aborda o cenário mais solicitado e pode tornar o código não verificável sem opção para reverter isso.

Questões por resolver

  • O atributo deve ser realmente emitido nos metadados?

Reuniões de design

Nenhum ainda.