Compartilhar via


Suprimir a emissão do sinalizador localsinit.

Nota

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação 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 reunião de design de idioma (LDM).

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

Resumo

Permitir a supressão da emissão do sinalizador localsinit por meio do atributo SkipLocalsInitAttribute.

Motivação

Contexto

Por especificação CLR, variáveis locais que não contêm referências não são inicializadas para um valor específico pela VM/JIT. A leitura dessas variáveis sem inicialização é segura por tipo, mas caso contrário, o comportamento é indefinido e específico da implementação. Normalmente, os locais não inicializados contêm os valores deixados na memória que agora está ocupada pelo quadro de pilha. Isso pode levar a um comportamento não determinístico e a erros 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 inicializado com zero. Nota: isso inclui tanto as variáveis locais quanto os dados stackalloc.

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

Historicamente, o compilador C# emite a flag localsinit em todos os métodos que declaram variáveis locais.

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

  • As regras CLR e C# podem não concordar se um argumento local deve ser passado pois um argumento out é um 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

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

Em alguns casos, o JIT pode omitir a inicialização com zero de variáveis locais individuais quando essa inicialização é substituída por atribuições subsequentes. Nem todos os JITs fazem isso e essa otimização tem limites. Isso não ajuda com stackalloc.

Para ilustrar que o problema é real - há um bug conhecido em que um método que não contém nenhum IL local não teria o sinalizador localsinit. O bug já está sendo explorado pelos usuários colocando stackalloc nesses métodos intencionalmente para evitar custos de inicialização. Isso ocorre apesar do fato de que a ausência de elementos locais IL é uma métrica instável e pode variar dependendo das alterações 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.

Design detalhado

Permitir especificar System.Runtime.CompilerServices.SkipLocalsInitAttribute como uma maneira de indicar ao compilador para não emitir o flag localsinit.

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

Os destinos de atributo 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á em qual assembly o atributo está definido.

Quando o atributo é especificado em um contêiner (class, module, que contém o 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 sinalizador afeta apenas a estratégia de geração de código para os corpos de métodos reais. Ou seja, o sinalizador não tem efeito sobre métodos abstratos e não é propagado para substituir/implementar métodos.

Esse é explicitamente um recurso do compilador e não um recurso de linguagem .
Semelhante aos switches de linha de comando do compilador, esta funcionalidade controla os detalhes de implementação de uma estratégia específica de geração de código e não precisa ser exigida pela especificação do C#.

Inconvenientes

  • Compiladores antigos/outros podem não honrar o atributo. Ignorar o atributo é um comportamento compatível. Isso pode resultar apenas em um leve impacto no desempenho.

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

  • Aplicar o 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

  • omita o sinalizador localinits quando o método for declarado no contexto unsafe. Isso pode causar uma alteração de comportamento silencioso e perigoso de determinístico para não determinístico em um caso de stackalloc.

  • omita sempre o sinalizador localinits. Ainda pior do que acima.

  • omita o flag localinits, a menos que o stackalloc seja utilizado dentro do corpo do método. Não aborda o cenário mais solicitado e pode tornar o código inverificável sem nenhuma opção para reverter isso.

Perguntas não resolvidas

  • O atributo deve ser de fato emitido para os metadados?

Reuniões de design

Nenhum ainda.