# Jan Potocki 2020

.globl filter
.type filter, @function

.data
# Pozwala uzyc rozkazu movdqa zamiast movdqu
.align 16

# Macierz k w 4 kopiach - do rejestru xmm zmiescimy dane dla 4 pikseli splotu 
# 1. wiersz macierzy dopelniony do 32 bitow
kernel128_1:
.byte -1, -1, 0, 0
.byte -1, -1, 0, 0
.byte -1, -1, 0, 0
.byte -1, -1, 0, 0
# 2. wiersz macierzy dopelniony do 32 bitow
kernel128_2:
.byte -1, 0, 1, 0
.byte -1, 0, 1, 0
.byte -1, 0, 1, 0
.byte -1, 0, 1, 0
# 3. wiersz macierzy dopelniony do 32 bitow
kernel128_3:
.byte 0, 1, 1, 0
.byte 0, 1, 1, 0
.byte 0, 1, 1, 0
.byte 0, 1, 1, 0

# Stala na potrzeby normalizacji - wartosc splotu z k miesci sie w przedziale 
# od 3*(-1*255) do 3*(1*255) czyli <-765,765>, wiec dodanie 765 pozwoli
# przesunac ja w przedzial <0,1530>.
# Na tym etapie obliczen liczby beda zapisane w 16-bitowej reprezentacji.
bias128: .word 765, 765, 765, 765, 765, 765, 765, 765

# Argumenty:
# rdi - adres M
# rsi - adres W
# rdx - rozdzielczosc w poziomie
# rcx - rozdzielczosc w pionie

.text

filter:
push %rbp
mov %rsp, %rbp
push %r15
push %r14
push %r13
push %r12

# r15 - warunek stopu dla petli SIMD, tyle ile zmiesci sie w wierszu
mov %rdx, %r15
sub $17, %r15
# r14 - warunek stopu dla petli indeksujacej wiersze
mov %rcx, %r14
sub $2, %r14
# r12 - warunek stopu dla petli pomocniczej, dokonczenie wiersza
mov %rdx, %r12
sub $2, %r12
# ...w tej implementacji skrajne piksele obrazu wynikowego pomijamy

# Wpisanie stalych do rejestrow aby przyspieszyc obliczenia
movdqa bias128, %xmm12
movdqa kernel128_1, %xmm13
movdqa kernel128_2, %xmm14
movdqa kernel128_3, %xmm15

# r9 - pomocniczy indeks adresujacy wiersze w pamieci
xor %r9, %r9
# r10 - indeks rozdzielczosci pionowej (wierszy)
xor %r10, %r10

# Petla indeksujaca wiersze
lp1:
# r11 - indeks rozdzielczosci poziomej (pikseli w wierszu)
xor %r11, %r11

# Podstawowa petla SIMD (16 pikseli na iteracje)
lp2:
# r8 - adres pikseli w pamieci (suma adresu wiersza i indeksu pikseli)
mov %r9, %r8
add %r11, %r8

# Instrukcja pinsrd kopiuje 4 kolejne 8-bitowe piksele do fragmentu xmm,
# czyli tyle ile trzeba aby obliczyc splot z jednym wierszem k. Ostatni piksel
# zachodzi na pierwszy kolejnego splotu - to nie ma znaczenia, bo kazdy wiersz
# macierzy k jest do 32-bitowej postaci dopelniony zerem na ostatnim bajcie.
# Instrukcja pmaddubsw oblicza iloczyn 8-bitowych pikseli obrazu i wartosci 
# kernela, sumuje dwa sasiednie iloczyny i w ich miejsce zapisuje wynik
# w 16-bitowej reprezentacji U2.

# Gorny wiersz macierzy k
pinsrd $0, 0(%rdi, %r8, 1), %xmm0
pinsrd $1, 1(%rdi, %r8, 1), %xmm0
pinsrd $2, 2(%rdi, %r8, 1), %xmm0
pinsrd $3, 3(%rdi, %r8, 1), %xmm0
pmaddubsw %xmm13, %xmm0

pinsrd $0, 4(%rdi, %r8, 1), %xmm1
pinsrd $1, 5(%rdi, %r8, 1), %xmm1
pinsrd $2, 6(%rdi, %r8, 1), %xmm1
pinsrd $3, 7(%rdi, %r8, 1), %xmm1
pmaddubsw %xmm13, %xmm1

pinsrd $0, 8(%rdi, %r8, 1), %xmm6
pinsrd $1, 9(%rdi, %r8, 1), %xmm6
pinsrd $2, 10(%rdi, %r8, 1), %xmm6
pinsrd $3, 11(%rdi, %r8, 1), %xmm6
pmaddubsw %xmm13, %xmm6

pinsrd $0, 12(%rdi, %r8, 1), %xmm7
pinsrd $1, 13(%rdi, %r8, 1), %xmm7
pinsrd $2, 14(%rdi, %r8, 1), %xmm7
pinsrd $3, 15(%rdi, %r8, 1), %xmm7
pmaddubsw %xmm13, %xmm7

add %rdx, %r8
# Przechowanie adresu srodkowego wiersza na pozniej, do zapisu wyniku splotu
mov %r8, %r13

# Srodkowy wiersz macierzy k
pinsrd $0, 0(%rdi, %r8, 1), %xmm2
pinsrd $1, 1(%rdi, %r8, 1), %xmm2
pinsrd $2, 2(%rdi, %r8, 1), %xmm2
pinsrd $3, 3(%rdi, %r8, 1), %xmm2
pmaddubsw %xmm14, %xmm2

pinsrd $0, 4(%rdi, %r8, 1), %xmm3
pinsrd $1, 5(%rdi, %r8, 1), %xmm3
pinsrd $2, 6(%rdi, %r8, 1), %xmm3
pinsrd $3, 7(%rdi, %r8, 1), %xmm3
pmaddubsw %xmm14, %xmm3

pinsrd $0, 8(%rdi, %r8, 1), %xmm8
pinsrd $1, 9(%rdi, %r8, 1), %xmm8
pinsrd $2, 10(%rdi, %r8, 1), %xmm8
pinsrd $3, 11(%rdi, %r8, 1), %xmm8
pmaddubsw %xmm14, %xmm8

pinsrd $0, 12(%rdi, %r8, 1), %xmm9
pinsrd $1, 13(%rdi, %r8, 1), %xmm9
pinsrd $2, 14(%rdi, %r8, 1), %xmm9
pinsrd $3, 15(%rdi, %r8, 1), %xmm9
pmaddubsw %xmm14, %xmm9

add %rdx, %r8

# Dolny wiersz macierzy k
pinsrd $0, 0(%rdi, %r8, 1), %xmm4
pinsrd $1, 1(%rdi, %r8, 1), %xmm4
pinsrd $2, 2(%rdi, %r8, 1), %xmm4
pinsrd $3, 3(%rdi, %r8, 1), %xmm4
pmaddubsw %xmm15, %xmm4

pinsrd $0, 4(%rdi, %r8, 1), %xmm5
pinsrd $1, 5(%rdi, %r8, 1), %xmm5
pinsrd $2, 6(%rdi, %r8, 1), %xmm5
pinsrd $3, 7(%rdi, %r8, 1), %xmm5
pmaddubsw %xmm15, %xmm5

pinsrd $0, 8(%rdi, %r8, 1), %xmm10
pinsrd $1, 9(%rdi, %r8, 1), %xmm10
pinsrd $2, 10(%rdi, %r8, 1), %xmm10
pinsrd $3, 11(%rdi, %r8, 1), %xmm10
pmaddubsw %xmm15, %xmm10

pinsrd $0, 12(%rdi, %r8, 1), %xmm11
pinsrd $1, 13(%rdi, %r8, 1), %xmm11
pinsrd $2, 14(%rdi, %r8, 1), %xmm11
pinsrd $3, 15(%rdi, %r8, 1), %xmm11
pmaddubsw %xmm15, %xmm11

# Suma kolumn splotu
paddw %xmm2, %xmm0
paddw %xmm3, %xmm1
paddw %xmm8, %xmm6
paddw %xmm9, %xmm7
paddw %xmm4, %xmm0
paddw %xmm5, %xmm1
paddw %xmm10, %xmm6
paddw %xmm11, %xmm7

# Suma wierszy splotu
phaddw %xmm1, %xmm0
phaddw %xmm7, %xmm6

# Normalizacja wyniku splotu - dodanie zdefiniowanego obciazenia i skalowanie
# z powrotem do 8-bitowej reprezentacji.
# Dokladny wynik pozwoliloby uzyskac dzielenie przez 6 (1530/6=255),
# przesuniecie o 3 bity w prawo jest wydajniejsze, a wciaz pozwala uzyskac
# rozsadna dokladnosc (1530>>3=191)
paddw %xmm12, %xmm0
paddw %xmm12, %xmm6
psrlw $3, %xmm0
psrlw $3, %xmm6

# Spakowanie 8-bitowych wynikow do jednego rejestru i zapis do pamieci
packuswb %xmm6, %xmm0
movdqu %xmm0, 1(%rsi, %r13, 1)

# Sprawdzenie, czy nastepna iteracja petli SIMD zmiesci sie w wierszu
add $16, %r11
cmp %r15, %r11
jl lp2

# Sprawdzenie, czy wiersz zostal przeliczony w calosci...
cmp %r12, %r11
je next_row

# ...jesli nie - na dobieg pomocnicza petla (1 piksel na iteracje)
lp2a:
mov %r9, %r8
add %r11, %r8

# Gorny wiersz macierzy k
pinsrd $0, 0(%rdi, %r8, 1), %xmm0
pmaddubsw %xmm13, %xmm0
add %rdx, %r8
mov %r8, %r13

# Srodkowy wiersz macierzy k
pinsrd $0, 0(%rdi, %r8, 1), %xmm2
pmaddubsw %xmm14, %xmm2
add %rdx, %r8

# Dolny wiersz macierzy k
pinsrd $0, 0(%rdi, %r8, 1), %xmm4
pmaddubsw %xmm15, %xmm4

# Suma i normalizacja splotu
paddw %xmm2, %xmm0
paddw %xmm4, %xmm0
phaddw %xmm0, %xmm0
paddw %xmm12, %xmm0
psrlw $3, %xmm0

# Spakowanie 8-bitowego wyniku w najmlodszym fragmencie rejestru i zapis
packuswb %xmm0, %xmm0
pextrb $0, %xmm0, 1(%rsi, %r13, 1)

# Sprawdzenie, czy tym razem wiersz jest juz przeliczony w calosci
inc %r11
cmp %r12, %r11
jl lp2a

next_row:
# Przesuniecie adresu wiersza w dol
add %rdx, %r9
# Sprawdzenie, czy caly obraz jest przeliczony
inc %r10
cmp %r14, %r10
jl lp1

pop %r12
pop %r13
pop %r14
pop %r15
pop %rbp
ret
