Поделиться через


Подавить генерацию флага localsinit.

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Включает предлагаемые изменения в спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия отражены в соответствующей заметке заседания по разработке языка (LDM).

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Сводка

Разрешить подавление эмиссии флага localsinit с помощью атрибута SkipLocalsInitAttribute.

Мотивация

Фон

Согласно спецификации CLR, локальные переменные, которые не содержат ссылок, не инициализируются до какого-либо определенного значения виртуальной машиной или JIT. Чтение из таких переменных без инициализации является типобезопасным, но в противном случае поведение не определено и зависит от реализации. Обычно неинициализированные локальные переменные содержат произвольные значения, оставшиеся в памяти, занятой кадром стека. Это может привести к недетерминированному поведению и ошибкам, которые трудно воспроизвести.

Существует два способа "назначить" локальную переменную:

  • сохранением значения или
  • При указании флага localsinit все, что выделено из локального пула памяти, будет инициализировано нулями. Примечание: это включает как локальные переменные, так и данные stackalloc.

Использование неинициализированных данных не рекомендуется и не допускается в проверяемом коде. Хотя можно было бы доказать это с помощью анализа потока, допускается, что алгоритм проверки будет консервативным и просто требовать, чтобы localsinit было установлено.

Исторически компилятор C# генерировал флаг localsinit для всех методов, объявляющих локальные переменные.

Хотя C# использует анализ строго определённого назначения, который является более строгим, чем того требует спецификация CLR (C# также необходимо учитывать область локальных переменных), не гарантируется, что полученный код будет формально проверяемым.

  • Правила CLR и C# могут не согласиться, допускается ли передача локальной переменной как аргумента outuse.
  • Правила CLR и C# могут не совпадать в вопросах обработки условных ветвей, когда условия известны (распространение констант).
  • CLR также может просто требовать localinits, поскольку это разрешено.

Проблема

В высокопроизводительном приложении затраты на принудительную инициализацию с нулевыми значениями могут быть заметны. Особенно заметно при использовании stackalloc.

В некоторых случаях JIT может опустить начальную инициализацию локальных переменных нулями, если такая инициализация "убита" последующими назначениями. Не все JIT это делают, и такая оптимизация имеет свои ограничения. Это не помогает с stackalloc.

Чтобы показать, что проблема реальна, существует известная ошибка, при которой метод, не содержащий никаких IL локальных переменных, не будет иметь флага localsinit. Ошибка уже эксплуатируется пользователями, помещая stackalloc в такие методы намеренно, чтобы избежать затрат на инициализацию. Несмотря на то, что отсутствие IL локальных переменных является нестабильной метрикой и может изменяться в зависимости от изменений в стратегии кодогенерации. Ошибка должна быть исправлена, и пользователи должны получить более документированные и надежные способы подавления флага.

Подробный дизайн

Разрешить указывать System.Runtime.CompilerServices.SkipLocalsInitAttribute как способ сообщить компилятору, чтобы он не выдавал флаг localsinit.

Результатом этого может стать то, что локальные переменные могут не быть инициализированы значением нуля JIT, что в большинстве случаев не наблюдается в C#.
Помимо этого stackalloc данные не будут инициализированы с нуля. Это определенно наблюдаемо, но также является наиболее мотивирующим сценарием.

Допустимые и распознанные целевые объекты атрибутов: Method, Property, Module, Class, Struct, Interface, Constructor. Однако компилятору не потребуется, чтобы атрибут был определен с указанными целевыми объектами, а также не будет заботиться о том, в какой сборке определен атрибут.

Если атрибут указан в контейнере (class, module, содержащий метод для вложенного метода, ...), флаг влияет на все методы, содержащиеся в контейнере.

Синтезированные методы "наследуют" флаг от логического контейнера или владельца.

Флаг влияет только на стратегию генерации кода для реальных тел методов. То есть флаг не влияет на абстрактные методы и не распространяется на методы, переопределяющие и реализующие.

Это явно компонент компилятора и не компонент языка.
Аналогично опциям командной строки компилятора, элементы управления функциями определяют детали реализации конкретной стратегии генерации кода и не требуют включения в спецификацию C#.

Недостатки

  • Старые или другие компиляторы могут не учитывать атрибут. Игнорирование атрибута является совместимым поведением. Может лишь привести к незначительной потере производительности.

  • Код без флага localinits может вызвать сбои проверки. Пользователи, запрашивающие эту функцию, обычно не обеспокоены проверкой.

  • Применение атрибута на более высоких уровнях, чем отдельный метод, имеет нелокальный эффект, который наблюдается при использовании stackalloc. Тем не менее, это самый запрошенный сценарий.

Альтернативы

  • Опускать флаг localinits, если метод объявлен в контексте unsafe. Это может вызвать тихое и опасное изменение поведения из детерминированного в недетерминированное в случае stackalloc.

  • всегда опустить флаг localinits. Еще хуже, чем выше.

  • опустить флаг localinits, если в тексте метода не используется stackalloc. Не решает наиболее часто запрашиваемый сценарий и может сделать код непроверяемым без возможности его восстановления обратно.

Неразрешенные вопросы

  • Должен ли атрибут фактически выводиться в метаданные?

Совещания по проектированию

Пока нет.