Избыточное хранилище в дизассемблируемом JIT

Я продолжаю исследовать вывод JIT-ассемблера и обнаружил пару странных инструкций по загрузке/сохранению:

mov    0x30(%rsp),%rdx ; <---- this load 

test   %edi,%edi
jne    0x00007fd3d27c5032

cmp    %r11d,%r10d
jae    0x00007fd3d27c4fbc
mov    0x10(%rbx,%r10,4),%edi
test   %edi,%edi
je     0x00007fd3d27c5062

mov    0xc(%rbp),%esi
test   %esi,%esi
je     0x00007fd3d27c4fea
mov    %r8d,0x1c(%rsp)
mov    %rdx,0x30(%rsp) ; <---- this store 

mov    %rax,0x28(%rsp)
mov    %ecx,0x10(%rsp)
mov    %rbp,0x20(%rsp)
mov    %rbx,0x8(%rsp)
mov    %r13d,%ebp
mov    %r10d,0x14(%rsp)
mov    %r11d,0x18(%rsp)
mov    %r14d,0x40(%rsp)
mov    %r9,(%rsp)
lea    (%r12,%rdi,8),%rdx

shl    $0x3,%rsi
callq  0x00007fd3caceaf00



mov    0x20(%rsp),%r11
mov    0x10(%r11),%r10d


mov    0x8(%r12,%r10,8),%r8d
cmp    $0xf2c10,%r8d
jne    0x00007fd3d27c4ffa

lea    (%r12,%r10,8),%r8

mov    0x10(%r8),%r10
movabs $0x7fffffffffffffff,%r9
cmp    %r9,%r10
je     0x00007fd3d27c5092

mov    %r10,%rdx
add    $0x1,%rdx

test   %rdx,%rdx
jle    0x00007fd3d27c50ce
mov    %r10,%rax
lock cmpxchg %rdx,0x10(%r8)
sete   %r11b
movzbl %r11b,%r11d

test   %r11d,%r11d
je     0x00007fd3d27c5116
test   %r10,%r10
jle    0x00007fd3d27c4f48



mov    0x108(%r15),%r11
mov    0x14(%rsp),%r10d
inc    %r10d

mov    0x1c(%rsp),%r8d
inc    %r8d
test   %eax,(%r11)


mov    (%rsp),%r9
mov    0x40(%rsp),%r14d
mov    0x18(%rsp),%r11d
mov    %ebp,%r13d
mov    0x8(%rsp),%rbx
mov    0x20(%rsp),%rbp
mov    0x10(%rsp),%ecx
mov    0x28(%rsp),%rax

movzbl 0x18(%r9),%edi
movslq %r8d,%rsi

cmp    0x30(%rsp),%rsi
jge    0x00007fd3d27c4f17

cmp    %r11d,%r10d
jl     0x00007fd3d27c4dea    ; this is the end of the loop
                             ; jump to the first instruction in this listing 

Зачем нужны эти инструкции? Нет работы с %rdx между загрузкой/сохранением. Да, это цикл, но я не понимаю, почему он может быть полезен и на следующих итерациях.

Это ошибка или такие же уловки JVM как в моем предыдущем вопросе?

Я нашел ту же проблему в этой статье но там нет никаких объяснений.

Полный PrintAssemble вы можете увидеть здесь, а также оригинал код здесь

Спасибо!


person QIvan    schedule 22.01.2019    source источник
comment
Вы уверены, что в этих блоках нет целевых ответвлений? Если нет, то да, это похоже на пропущенную оптимизацию, но это не редкость в JIT-коде. Это выглядит легко для компилятора, однако, опять же, если нет никакой возможности, чтобы что-то еще могло разветвляться в какой-либо из базовых блоков после загрузки, и если компилятор может легко доказать это.   -  person Peter Cordes    schedule 23.01.2019
comment
Это не полное тело nmethod. Можете ли вы опубликовать полный вывод PrintAssembly, включая заглушки?   -  person apangin    schedule 23.01.2019
comment
думаю нет, по крайней мере перфазм мне их не показал... Вот рисует стрелки для джумов github.com/QIvan/reactive-hardcore/blob/master/result.txt#L83   -  person QIvan    schedule 23.01.2019
comment
perfasm показывает только самые горячие регионы. Это еще не означает, что нет входящей ветки откуда-то еще (например, из заглушки).   -  person apangin    schedule 23.01.2019
comment
извините, что заглушки вы имеете в виду? Это полный вывод запуска jmh с ключом perfasm github.com/QIvan/ реактивный хардкор/blob/master/result.txt   -  person QIvan    schedule 23.01.2019
comment
ок, попробую распечатать и добавить в репозиторий   -  person QIvan    schedule 23.01.2019


Ответы (1)


Я воспроизвел полный ассемблерный код для ArraySubscription.slowPath. Хотя сопоставление регистров немного отличается от вашего фрагмента, структура кода точно такая же.

Неполный фрагмент привел вас к неверному выводу. На самом деле %rdx может изменяться между загрузкой и сохранением, потому что в середине есть цель ветки: L219 -> L55

Это становится вполне понятным, если посмотреть на соответствующий Исходный код Java:

        while (true) {
            for (; sent < n && idx < length; sent++, idx++) {
                if (canceled) {
                    return;
                }

                T element = array[idx];

                if (element == null) {
                    subscriber.onError(new NullPointerException());
                    return;
                }

                subscriber.onNext(element);
            }

Perfasm показал вам скомпилированный код горячего внутреннего цикла for. Значение в 0x30(%rsp), которое также кэшируется в %rdx, содержит локальную переменную n. Но затем после цикла значение n меняется:

            n = requested;

и внешний while продолжается. соответствующий скомпилированный код обновляет n только в регистре, а не в 0x30(%rsp).

person apangin    schedule 23.01.2019
comment
Ух ты! Это было действительно потрясающе! Я хочу когда-нибудь иметь возможность понимать код сборки, как вы) Извините, вчера я потратил некоторое время на борьбу с выводом PrintAssemble в jmh (дополнительное спасибо за ответ stackoverflow.com/a/36293812/2406992), и когда я получу полный код сборки github.com/QIvan/reactive-hardcore/blob/master/ было слишком поздно, и я хотел сначала изучить его самостоятельно. И... сегодня я получил те же результаты, что и вы, и пришел сюда, чтобы написать, что я был идиотом... В любом случае, большое спасибо за ваш ответ! - person QIvan; 24.01.2019
comment
Кстати, как вы напечатали код сборки в синтаксисе INTEL вместо AT&T? - person QIvan; 24.01.2019
comment
@QIvan Вот оно - person apangin; 24.01.2019