05.1 Conspect (ru)

Повторим массивы:

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

Доступ к массиву к элементу массива очень просто устроен потому что мы можем просто взять начальные индекс прибавить к нему индекс умноженный на размер одного элемента и тем самым посчитать положение элемента. Это кстати приводит к тому что если мы работаем с целочисленными массивный то у нас как правило у нас как правило прибавляют 4.

# text section starts
#    …
        li    $t1 5
        li    $t2 6
        li    $t3 7
# now calling subroutine
        jal    treug
# do something else
#    …
# Exit from orpgram
        li     $v0 10
        syscall
#    …
# Subroutiine
treug:  move   $t0 $zero
        add    $t4 $t1 $t2
        add    $t5 $t2 $t3
        add    $t6 $t3 $t1
        bgt    $t3 $t4 not
        bgt    $t1 $t5 not
        bgt    $t2 $t6 not
        li     $t0 1
not:    jr     $ra

Псевдо инструкция la которая в точности совпадает с инструкцией li фактически ей является. Отличается от lа только семантикой только

тем как мы из толкуем программу.

Нас на языке ассемблера li говорит нам о том что программист захотел загрузить в регистр просто число а la говорит о том что он туда захотелось грузить адрес.

Зачем нужны подпрограммы?

Они же функции они же процедуры.

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

Про первый подход: это обеспечить возможность переходить куда-то а потом возвращаться обратно.

Второй подход: копи паста.

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

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

Значит надо договориться что какие-то регистры при вызове не трогаются.

Там 32 штуки из них использовать можно 20.

Мы можем договориться что какое-то количество регистров вы можем передавать как параметры в процедуру подпрограмм.

Функция: возвращает значение одинарной или даже двойной точности. Подпрограмма являются частью вашего кода. Ее можно выполнять больше чем один раз. Она лежит где-то отдельно в нашем коде мы на нее перепрыгиваем с помощью специальной команды jump.

Jump on link собственно который мы будем использовать в подавляющем большинстве случаев мы указываем смещение.

Глупо использовать разные регистры в качестве адреса возврата - мы потом сами запутаемся. Возвращение возврат из функций будет выполняться просто с помощью функций jr это jump регистр.

Обязаны не трогать регистр ra.

Не решили проблему последующих вызовов из данной подпрограммы и вызовов рекурсивных когда подпрограммы вызывать сама себя. ra прежде чем вызывать следующий jump in link а потом его оттуда вспомнить.

Мы не определили какие регистры у нас считаются временными .

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

Код только на этот раз оформленный в соответствии конвенции:

#    …
        li    $a0 5
        li    $a1 6
        li    $a2 7
#    …
        jal    treug
# keep the result in static register
        move    $s1 $v0
#    …
        li     $v0 10
        syscall
# Subroutine
treug:  move   $v0 $zero
        add    $t4 $a0 $a1
        add    $t5 $a1 $a2
        add    $t6 $a2 $a0
        bgt    $a2 $t4 not
        bgt    $a0 $t5 not
        bgt    $a1 $t6 not
        li     $v0 1
not:    jr     $ra

а именно параметры передаются через регистры вида а, их три штуки а0 а1 а2, возвращаемое значение передается через регистр v0.

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

Про функции:

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

Решения: поставить их в конце или сделать entry point.

Вы должны всегда помнить что слово not у вас уже зарезервировано.

Метки это просто замена адресам.

Про стек:

Мы туда можем положить элемент по одной штуке и это называется положить в на вершину стека и мы можем оттуда их снимать то есть забирать. То что там последние лежит то что у нас last in.

Операция положить в стек называется push, операция снять со стека называется поп. Хотя конкретно в ассемблере этих операций нету.

Все элементы стека также как элемента массива одинакового размера.

Стек который хранит элементы разного размера тогда должен хранить еще и размер.

Если мы собираемся реализовать так аппаратно у нас должна быть такая сущность в которой лежит адрес вершины стека текущий адрес вершина.

Мы можем придумать архитектуру в которой операции push и операция поп будут реализованы аппаратно то есть будет специально отдельный регистр.

Использование 29 регистра для наших дел это просто договорённость. Пока память для стека используется самая обычная. Никакой специальной памяти для стека.

Стек начинается самого вверх но чтобы ситуация переполнение стека не приводила сразу к исключению.

Мы получим адрес который начинается уже на восьмёрку а это kernels и попытка туда сходить приведет к исключению. Добавлен небольшой буфер.

Изначально когда мы запускаем программу стек pointer у нас равен именно вот этому числу 0x7fffeffc.

Стек растет сверху вниз.

Пример всевозможных команд работа со стеком.

li              $t1,            123          # something
addi            $sp,            $sp,    -4   # push.1
sw              $t1,            ($sp)        # push.2
addi            $t2,            $t1,    100  # another something
addi            $sp,            $sp,    -4   # push.1
sw              $t2,            ($sp)        # push.2
lw              $t3,            ($sp)        # read stack edge
lw              $t4,            4($sp)       # read second stack value
lw              $t0,            ($sp)        # pop.1
addi            $sp,            $sp,    4    # pop.2
addi            $sp,            $sp,    -4   # push.1
sw              $zero,          ($sp)        # push.2

Чтобы прочесть не вершину стека какой-то и на его элемент вы просто берем умножаем ее на 4 делаем такой офсет.

Нет никакого автоматической зачистки стек.

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

Стек это штука части чувствительная к порче.

Операции со стеком:

.data
n:      .word 7
res:    .word 0

.text
# call fact:
        lw      $a0 n
        jal     fact
        sw      $v0 res

fact:   addiu   $sp $sp -4      # keep $ra
        sw      $ra ($sp)
        addiu   $sp $sp -4      # keep $s0
        sw      $s0 ($sp)

        move    $s0 $a0         # calculate n-1
        subi    $a0 $a0, 1
        ble     $a0 1 done      # if n<2, done

        jal     fact            # calculate fact(n-1)
        mul     $s0 $s0 $v0     # $s0 survives a call

done:   move    $v0, $s0        # return value
        lw      $s0 ($sp)       # restore $s0
        addiu   $sp $sp 4
        lw      $ra ($sp)       # restore $ra
        addiu   $sp $sp 4
        jr      $ra

Пролог: Начальная часть кода подпрограммы, которая хранит состояние программы (хранит регистры и т. д.). Эпилог: Заключительная часть кода подпрограммы, которая восстанавливает состояние программы (загружает регистры и т. д.). Преамбула: Часть кода вызывающей части непосредственно перед подпрограммой jal. Подготавливает некоторую среду, специфичную для подпрограммы.

Пролог это начало нашей подпрограммы которые выполняют часть вот этих вот конвенциональных действий.

Эпилог это часть нашей подпрограммы который выполняет к ментальные действия.

Преамбула это тот код который выполняется в основной программе перед вызовом .

Пример:

fact:   addiu   $sp $sp -4      # keep $ra
        sw      $ra ($sp)

        move    $t0 $a0
        subi    $a0 $a0, 1
        ble     $a0 1 done

        addiu   $sp $sp -4      # keep N
        sw      $t0 ($sp)
        jal     fact
        lw      $t1 ($sp)       # keep N
        addiu   $sp $sp 4
        mul     $t0 $t1 $v0

done:   move    $v0, $t0        # return value
        lw      $ra ($sp)       # restore $ra
        addiu   $sp $sp 4
        jr      $ra

HSE/ArchitectureASM/05_StackAndSubroutines/Conspect (последним исправлял пользователь FrBrGeorge 2020-06-24 20:10:30)