2016-06-28 4 views
1

Этот вопрос является дополнительным вопросом на Can the C compiler optimizer violate short-circuiting and reorder memory accesses for operands in a logical-AND expression?.Как спекулятивная ошибка из-за оптимизации компилятора реализована под капотом?

Рассмотрите следующий код.

if (*p && *q) { 
    /* do something */ 
} 

Теперь согласно дискуссии на Can the C compiler optimizer violate short-circuiting and reorder memory accesses for operands in a logical-AND expression? (особенно Дэвид Шварц комментарий и ответ) можно для оптимизатора стандартных-совместимых C компиляторов испускать инструкции центрального процессора, который обращается к *q до того *p при сохранении наблюдаемого поведения точки последовательности, установленной с оператором &&.

Поэтому, хотя оптимизатор может испускать код, который получает доступ к *q перед тем *p, она по-прежнему необходимо обеспечить, чтобы любые побочные эффекты *q (такие как ошибки сегментации) наблюдается только тогда, когда *p не равен нулю. Если *p равно нулю, то ошибка, вызванная *q, не должна наблюдаться, т. Е. Произойдет спекулятивная ошибка сначала из-за того, что сначала выполняется на CPU *q, но спекулятивная ошибка будет проигнорирована, как только выполняется *p и считается 0.

Мой вопрос: как эта спекулятивная ошибка выполняется под капотом?

Я был бы признателен, если бы вы могли наложить больше света на следующие моменты, отвечая на этот вопрос.

  1. Насколько я знаю, когда процессор обнаруживает ошибку, он генерирует ловушку, что ядро ​​должно обработать (либо принять меры для восстановления, такие как страницы подкачки, или сигнал о неисправности, такие как SIGSEGV процессу) , Я прав?
  2. Итак, если компилятор должен испускать код для выполнения спекулятивной ошибки, мне кажется, что ядро ​​и компилятор (и, возможно, процессор тоже) должны взаимодействовать друг с другом для реализации спекулятивной ошибки. Как компилятор испускает инструкции, которые указывают ядру или процессору, что ошибка, вызванная кодом, должна считаться спекулятивной?
+1

Если '* q' обращается безоговорочно * до или после' if (* p && * q) ', компилятор может сделать вывод о том, что доступ не может быть вызван ошибкой в ​​соответствующей программе и, следовательно, изменить порядок доступа. – EOF

ответ

3

Он реализован как часть обычного спекулятивного процесса получения. Результат спекулятивной выборки, будь то численный результат или ошибка, является умозрительным. Он используется, если и только тогда, когда это необходимо позже.

Насколько я знаю, когда процессор обнаруживает ошибку, он генерирует ловушку, что ядро ​​должно обработать (либо принять меры для восстановления, такие как страницы подкачки, или сигнал о неисправности, такие как SIGSEGV процессу) , Я прав?

Результатом выполнения неспелуалистически выборки, вызывающей ошибку, является ловушка. Результат выполнения выборки производит ошибку с умозрительным эффектом - это спекулятивная ловушка, которая будет фактически возникать только в том случае, если используется результат спекулятивной выборки. Если вы подумаете об этом, спекулятивные выборки будут невозможны без этого механизма.

Поэтому, если компилятор должен испускать код для выполнения спекулятивной ошибки, мне кажется, что ядро ​​и компилятор (и, возможно, процессор тоже) должны взаимодействовать друг с другом для реализации спекулятивной ошибки. Как компилятор испускает инструкции, которые указывают ядру или процессору, что ошибка, вызванная кодом, должна считаться спекулятивной?

Компилятор делает это путем размещения выборки для *q после испытания на результат *p. Это сигнализирует CPU, что выборка является спекулятивной и что она может использовать результаты только после того, как будет получен результат теста по результату *p.

ЦП может и выполняет выполнение выборки *q, прежде чем он узнает, нуждается она в этом или нет. Это почти необходимо, потому что выборка может потребовать межъядерных операций, которые бывают медленными - вы не захотите ждать дольше, чем нужно. Таким образом, современные многоядерные процессоры реализуют агрессивную спекулятивную выборку.

Это то, что делают современные процессоры. (Ответ для процессоров с явными операциями спекулятивной выборки различен.)

+0

'Компилятор делает это, помещая выборку для * q после теста на результат * p.'. Но предпосылка моего вопроса - это сценарий, в котором оптимизатор компилятора испускает код для извлечения' * q' перед извлечением '* p'. Я имею в виду: мой вопрос заключается не в том, что CPU выполняет инструкции не по порядку. Мой вопрос заключается в том, как компилятор поддерживает правильное наблюдаемое поведение при переупорядочении инструкций (размещение '* q' перед' * p'). –

+0

Ваша посылка - это нонсенс. Если обнаруживается разница, то компилятор сломан. – gnasher729

+0

@ LoneLearner Да. Компилятор испускает код для извлечения '* q' перед' * p', выдавая команды извлечения в другом порядке. Компилятор поддерживает правильное наблюдаемое поведение, потому что ЦП правильно реализует спекулятивную выборку. Автор компилятора понимает, как работает ЦП и издает инструкции, которые делают ЦП делать то, что он делает, когда он получает эти инструкции. Они не конкурируют, они на 100% сотрудничают. –

2

В C и C++ у вас есть правило «как есть», что означает, что компилятор может делать все, что ему нравится, пока наблюдаемое поведение является тем, что язык обещает.

Если компилятор генерирует код для древнего процессора без защиты памяти, где чтение * д будет читать что-то (неопределенное значение) без каких-либо побочных эффектов, то ясно, что можно читать * д, и даже обмен на это порядок испытаний. Точно так же как любой компилятор может обменивать операнды в (x> 0 || y> 0), если y имеет определенное значение или чтение y с неопределенным значением не имеет побочного эффекта.

Но вы спрашиваете о спекулятивном исполнении в процессоре. Ну, процессоры do выполняют инструкции после условных ветвей, прежде чем они узнают, была ли занята условная ветка или нет, но они делают 100% уверенными, что это не приводит к появлению побочных эффектов. Существует never любой код для этого, все это в CPU. Если условное выполнение делает что-то, что должно создать ловушку, тогда процессор ждет, пока он точно не узнает, была ли занята ветка или нет, а затем она либо берет ловушку, либо нет. Ваш код не видит, и даже ОС не видит его.

+0

"* Для этого не существует никакого кода, все это в CPU *« Это бессмысленно. Если какой-то код заставляет ЦП делать что-то, то этот код является кодом для того, что он сделал для ЦП. –