GHC генерирует избыточные основные операции

У меня есть следующая программа для преобразования 6-битного ASCII в двоичный формат.

ascii2bin :: Char -> B.ByteString
ascii2bin = B.reverse . fst . B.unfoldrN 6 decomp . to6BitASCII -- replace to6BitASCII with ord if you want to compile this
    where decomp n = case quotRem n 2 of (q,r) -> Just (chr r,q)

bs2bin :: B.ByteString -> B.ByteString
bs2bin = B.concatMap ascii2bin

это создает следующий сегмент ядра:

Rec {
$wa
$wa =
  \ ww ww1 ww2 w ->
    case ww2 of wild {
      __DEFAULT ->
        let {
          wild2
          wild2 = remInt# ww1 2 } in
        case leWord# (int2Word# wild2) (__word 1114111) of _ { 
          False -> (lvl2 wild2) `cast` ...;                                                                                   
          True ->
            case writeWord8OffAddr#
                   ww 0 (narrow8Word# (int2Word# (ord# (chr# wild2)))) w
            of s2 { __DEFAULT ->
            $wa (plusAddr# ww 1) (quotInt# ww1 2) (+# wild 1) s2
            }   
        };  
      6 -> (# w, (lvl, lvl1, Just (I# ww1)) #)
    }   
end Rec }

обратите внимание, что ord . chr == id, и поэтому здесь существует избыточная операция: narrow8Word# (int2Word# (ord# (chr# wild2)))

Есть ли причина, по которой GHC бесполезно преобразуется из Int → Char → Int, или это пример плохого генерации кода? Можно ли это оптимизировать?

EDIT: используется GHC 7.4.2, я не пробовал компилироваться с любой другой версией. С тех пор я обнаружил, что проблема остается в GHC 7.6.2, но избыточные операции удаляются в текущей ветки HEAD на github.

Ответ 1

Есть ли причина, по которой GHC бесполезно преобразуется из Int -> Char -> Int, или это пример плохого генерации кода? Можно ли это оптимизировать?

Не совсем (для обоих). Ядро, которое вы получаете от -ddump-simpl, еще не конец. После этого на пути к ассемблеру еще несколько оптимизаций и преобразований. Но удаление избыточных конверсий здесь на самом деле не является оптимизацией.

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

Сборка, которую 7.6.1 создает из кода [более читаемая, чем то, что производит 7.4.2, поэтому я принимаю это) - с ord вместо to6BitASCII -

ASCII.$wa_info:
_cXT:
    addq $64,%r12
    cmpq 144(%r13),%r12
    ja _cXX
    movq %rdi,%rcx
    cmpq $6,%rdi
    jne _cXZ
    movq $GHC.Types.I#_con_info,-56(%r12)
    movq %rsi,-48(%r12)
    movq $Data.Maybe.Just_con_info,-40(%r12)
    leaq -55(%r12),%rax
    movq %rax,-32(%r12)
    movq $(,,)_con_info,-24(%r12)
    movq $lvl1_rVq_closure+1,-16(%r12)
    movq $lvl_rVp_closure+1,-8(%r12)
    leaq -38(%r12),%rax
    movq %rax,0(%r12)
    leaq -23(%r12),%rbx
    jmp *0(%rbp)
_cXX:
    movq $64,192(%r13)
_cXV:
    movl $ASCII.$wa_closure,%ebx
    jmp *-8(%r13)
_cXZ:
    movl $2,%ebx
    movq %rsi,%rax
    cqto
    idivq %rbx
    movq %rax,%rsi
    cmpq $1114111,%rdx
    jbe _cY2
    movq %rdx,%r14
    addq $-64,%r12
    jmp GHC.Char.chr2_info
_cY2:
    movb %dl,(%r14)
    incq %r14
    leaq 1(%rcx),%rdi
    addq $-64,%r12
    jmp ASCII.$wa_info
    .size ASCII.$wa_info, .-ASCII.$wa_info

Часть, в которой narrow8Word# (int2Word# (ord# (chr# wild2))) появляется в ядре, после cmpq $1114111, %rdx. Если коэффициент не выходит за пределы диапазона, код переходит на _cY2, который больше не содержит таких преобразований. Байт записывается в массив, некоторые указатели/счетчики увеличиваются и переходят в начало.

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