Подтвердить что ты не робот

Отключение JIT в Safari 6 для обхода серьезных Javascript JIT-ошибок

Мы обнаружили серьезную проблему с интерпретацией нашего Javascript-кода, который встречается только на iOS 5/Safari 6 (а затем на нынешнем выпуске iPad), который, по нашему мнению, обусловлен критической ошибкой компилятора Just in Time JS в Safari. (См. обновления ниже для более уязвимых версий и версий, которые теперь содержат исправление).

Мы изначально обнаружили проблему в нашей онлайн-версии демонстрации нашей библиотеки: демоверсии более или менее случайны, но это происходит только во второй раз (или даже позже), что тот же код выполняется. То есть если вы запустите часть кода один раз, все будет работать нормально, однако последующие запускают сбой приложения.

Интересно, что выполнение одного и того же кода в Chrome для iOS не показывает проблему, которая, как мы полагаем, связана с отсутствующими возможностями JIT Webview, которые используются в Chrome для iOS.

После многих попыток мы, наконец, думаем, что нашли хотя бы один проблемный фрагмент кода:

  var a = 0; // counter for index
  for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
    b.$f = a++; // assign index to cell and then increment 

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

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

Я создал скрипку, которая показывает проблему здесь: http://jsfiddle.net/yGuy/L6t5G/

Запуск примера на iPad 2 с iOS 6 и все обновления, результат в порядке для первых двух запусков в моем случае, а в третьем идентификаторе запускается внезапно, последний элемент в списке имеет назначенное значение, которое отключено на один (выход при нажатии кнопки "кликнуть меня" изменяется от "от 0 до 500" до "от 0 до 501" )

Интересно, если вы переключите вкладки или подождите немного, это может случиться, что внезапно результаты верны для двух или более запусков! Кажется, что Safari иногда сбрасывает кеши JIT.

Итак, поскольку я думаю, что команда Safari может занять очень много времени, чтобы исправить эту ошибку (о которой я еще не сообщал), и могут быть другие подобные ошибки, подобные этой, скрывающейся в JIT, которые так же трудно найти, я хотел бы знать, есть ли способ отключить JIT-функциональность в Safari. Конечно, это замедлит наш код (который уже очень интенсивно работает с ЦП), но лучше медленнее, чем сбой.

Обновление: Неудивительно, что это затронуло не только оператор post increment, но и оператор пост декремента. Менее удивительно и более тревожно то, что не имеет значения, назначено ли значение, поэтому поиска в существующем коде недостаточно. Например. следующий код b.$f = (a++ % 2 == 0) ? 1 : 2;, где значение переменных не назначено, а просто используется для условия тернарного оператора, также "не удается" в том смысле, что иногда выбирается неправильная ветвь. В настоящее время кажется, что проблему можно избежать, только если почтовые операторы вообще не используются.

Обновление: Эта же проблема не только существует на устройствах iOS, но и также на Mac OSX в Safari 6 и последнем Safari 5: Они были протестированы и обнаружены в результате ошибки: Mac OS 10.7.4, Safari 5.1.7 Mac OS X 10.8.2, WebKit Ночной r132968: Safari 6.0.1 (8536.26.14, 537+). Интересно, что это не кажется затронутым: iPad 2 (Mobile) Safari 5.1.7 и iPad 1 Mobile Safari 5.1. Я сообщил об этих проблемах Apple, но пока не получил ответа.

Обновление: Об ошибке сообщается как ошибка Webkit 109036. Apple все еще не ответила на мой отчет об ошибках, все текущие версии (версии для Safari в феврале 2013 года) на iOS и MacOS по-прежнему подвержены этой проблеме.

Обновление 27 февраля 2013 года: Кажется, что ошибка была зафиксирована командой Webkit здесь! Это была действительно проблема с JIT и пост-операторами! Комментарии указывают на то, что ошибка может быть связана с большим количеством кода, поэтому возможно, что теперь были установлены более загадочные Heisenbugs!

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

4b9b3361

Ответ 1

Блоки try-catch, по-видимому, отключают JIT-компилятор на Safari 6 на Lion для части непосредственно внутри блока try (этот код работал у меня на Safari 6.0.1 7536.26.14 ​​и OS X Lion).

// test function
utility.test = function(){
    try {
        var a = 0; // counter for index
        for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
            b.$f = a++; // assign index to cell and then increment
    }
    catch (e) { throw e }
    this.$f5 = !1; // random code
};

Это, по крайней мере, документальное поведение текущей версии Google V8 (см. презентация ввода-вывода Google на V8), но я не знаю, Safari.

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

Хорошая работа по созданию этого воспроизводимого!

Ответ 2

IMO, правильное решение - сообщить об ошибке Apple, затем обменивайтесь ею в своем коде (конечно, использование отдельного оператора a = a + 1; будет работать, если JIT еще хуже, чем вы думали!). Однако это действительно сосать. Вот список распространенных вещей, которые вы также можете попробовать бросить в функцию, чтобы сделать ее де-оптимизацией и не использовать JIT:

  • Исключения
  • 'with' statement
  • с использованием аргумента object, например. arguments.callee
  • Eval()

Проблема с этими проблемами заключается в том, что механизм Javascript оптимизирован для JIT, прежде чем исправить эту ошибку, и в этом случае вы вернетесь к сбою. Итак, отчет и обходной путь!

Ответ 3

Собственно, ошибка цикла FOR по-прежнему присутствует в Safari на iOS 7.0.4 в iPhone 4 и iPad 2. Провал цикла может быть значительно проще, чем приведенный выше рисунок, и он пропускает несколько проходов через код, который нужно нажать. Переход в цикл WHILE обеспечивает правильное выполнение.

Ошибка кода:

function zf(num,digs) 
{ 
var out = ""; 
var n = Math.abs(num); 
for (digs;  digs>0||n>0; digs--)
{ 
    out = n%10 + out; 
    n = Math.floor(n/10); 
}  
return num<0?"-"+out:out; 
} 

Успешный код:

function zf(num,digs) 
{ 
var out = ""; 
var n = Math.abs(num); 
do 
{ 
    out = n%10 + out; 
    n = Math.floor(n/10); 
} 
while (--digs>0||n>0) 
return num<0?"-"+out:out; 
}