Tentando entender a opção do gcc -fomit-frame-pointer

Pedi ao Google que me desse o significado da opção gcc -fomit-frame-pointer , que me redireciona para a declaração abaixo.

-fomit-frame-pointer

Não mantenha o ponteiro do quadro em um registrador para funções que não precisam de um. Isso evita as instruções para salvar, configurar e restaurar pointers de frameworks; Ele também disponibiliza um registro extra em muitas funções. Isso também torna a debugging impossível em algumas máquinas.

Conforme meu conhecimento de cada function, um registro de ativação será criado na pilha da memory do processo para manter todas as variables ​​locais e mais algumas informações. Espero que este ponteiro de quadro signifique o endereço do registro de ativação de uma function.

Nesse caso, quais são os tipos de funções, para os quais não é necessário manter o ponteiro de frameworks em um registro? Se eu obtiver essa informação, tentarei projetar a nova function com base nisso (se possível), porque se o ponteiro do quadro não for mantido nos registradores, algumas instruções serão omitidas no binário. Isso realmente melhorará o desempenho visivelmente em um aplicativo em que há muitas funções.

A maioria das funções menores não precisa de um ponteiro de quadro – funções maiores podem precisar de um.

É realmente sobre o quão bem o compilador consegue controlar como a pilha é usada, e onde as coisas estão na pilha (variables ​​locais, argumentos passados ​​para a function atual e argumentos sendo preparados para uma function prestes a ser chamada). Eu não acho que seja fácil caracterizar as funções que precisam ou não precisam de um frame pointer (tecnicamente, a function NO tem que ter um frame pointer – é mais um caso de “se o compilador achar necessário reduzir a complexidade de outro código “).

Eu não acho que você deveria “tentar fazer com que funções não tenham um ponteiro de frame” como parte de sua estratégia de codificação – como eu disse, funções simples não precisam delas, então use -fomit-frame-pointer , e you ‘ ll obterá mais um registro disponível para o alocador de registro e salve 1-3 instruções sobre input / saída para funções. Se sua function precisa de um ponteiro de quadro, é porque o compilador decide que é uma opção melhor do que não usar um ponteiro de quadro. Não é um objective ter funções sem um ponteiro de quadro, é um objective ter um código que funcione corretamente e rapidamente.

Note que “não ter um ponteiro de frame” deve dar um melhor desempenho, mas não é uma mágica que traz melhorias enormes – particularmente não no x86-64, que já tem 16 registros para começar. Em x86 de 32 bits, uma vez que ele possui apenas 8 registradores, um dos quais é o ponteiro da pilha e ocupa outro como o ponteiro do quadro significa que 25% do espaço de registro é obtido. Para mudar isso para 12,5% é uma grande melhoria. É claro que compilar para 64 bits também ajudará bastante.

Isso é tudo sobre o registro BP / EBP / RBP nas plataformas Intel. Este registrador padrão para o segmento de pilha (não precisa de um prefixo especial para acessar o segmento da pilha).

O EBP é a melhor opção de registro para acessar estruturas de dados, variables ​​e espaço de trabalho alocado dinamicamente dentro da pilha. O EBP é frequentemente usado para acessar elementos na pilha em relação a um ponto fixo na pilha, em vez de em relação aos TOS atuais. Geralmente, identifica o endereço base do quadro de pilha atual estabelecido para o procedimento atual. Quando EBP é usado como o registro base em um cálculo de offset, o offset é calculado automaticamente no segmento atual da pilha (ou seja, o segmento atualmente selecionado por SS). Como o SS não precisa ser explicitamente especificado, a codificação de instruções nesses casos é mais eficiente. O EBP também pode ser usado para indexar em segmentos endereçáveis ​​através de outros registradores de segmento.

(fonte: http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )

Como na maioria das plataformas de 32 bits, o segmento de dados e o segmento de pilha são os mesmos, essa associação de EBP / RBP com a pilha não é mais um problema. O mesmo ocorre em plataformas de 64 bits: A arquitetura x86-64, lançada pela AMD em 2003, praticamente abandonou o suporte à segmentação no modo de 64 bits: quatro dos registros de segmento: CS, SS, DS e ES são forçados a 0 Estas circunstâncias das plataformas x86 de 32 e 64 bits significam essencialmente que o registrador EBP / RBP pode ser usado, sem qualquer prefixo, nas instruções do processador que acessam a memory.

Assim, a opção de compilador que você escreveu permite que o BP / EBP / RBP seja usado para outros meios, por exemplo, para manter uma variável local.

Por “Isso evita as instruções para salvar, configurar e restaurar pointers de quadro” significa evitar o código a seguir na input de cada function:

 push ebp mov ebp, esp 

ou a instrução enter , que foi muito útil nos processadores Intel 80286 e 80386.

Além disso, antes do retorno da function, o seguinte código é usado:

 mov esp, ebp pop ebp 

ou a instrução leave .

As ferramentas de debugging podem varrer os dados da pilha e usar esses dados de registro EBP empurrados enquanto localizam call sites , ou seja, para exibir nomes da function e os argumentos na ordem em que foram chamados hierarquicamente.

Programadores podem ter perguntas sobre frames de pilha não em um termo amplo (que é uma única entidade na pilha que serve apenas uma chamada de function e mantém o endereço de retorno, argumentos e variables ​​locais) mas em um sentido restrito – quando o termo stack frames é mencionado no contexto das opções do compilador. Do ponto de vista do compilador, um quadro de pilha é apenas o código de input e saída da rotina , que empurra uma âncora para a pilha – que também pode ser usada para debugging e exception handling. As ferramentas de debugging podem varrer os dados da pilha e usar essas âncoras para rastreamento de volta, enquanto localizam call sites na pilha, ou seja, para exibir nomes da function na ordem em que foram chamados hierarquicamente.

É por isso que é muito importante entender para um programador o que é um quadro de pilha em termos de opções do compilador – porque o compilador pode controlar se deve gerar este código ou não.

Em alguns casos, o quadro da pilha (código de input e saída da rotina) pode ser omitido pelo compilador, e as variables ​​serão acessadas diretamente pelo ponteiro da pilha (SP / ESP / RSP) em vez do conveniente ponteiro base (BP / ESP / RSP). Condições para um compilador omitir os frameworks de pilha para algumas funções podem ser diferentes, por exemplo: (1) a function é uma function folha (ou seja, uma entidade final que não chama outras funções); (2) sem exceções são usadas; (3) nenhuma rotina é chamada com parâmetros de saída na pilha; (4) a function não tem parâmetros.

Omitir frameworks de pilha (código de input e saída para a rotina) pode tornar o código menor e mais rápido, mas também pode afetar negativamente a capacidade dos depuradores de rastrear os dados na pilha e exibi-los ao programador. Essas são as opções do compilador que determinam sob quais condições uma function deve satisfazer para que o compilador a atribua com a input do quadro da pilha e o código de saída. Por exemplo, um compilador pode ter opções para adicionar tal código de input e saída a funções nos seguintes casos: (a) sempre, (b) nunca, (c) quando necessário (especificando as condições).

Retornando de generalidades para particularidades: se você usar a -fomit-frame-pointer compilador GCC -fomit-frame-pointer , você pode ganhar tanto o código de input quanto o código de saída da rotina e ter um registro adicional (a menos que ele já esteja ativado por padrão seja ele próprio ou implicitamente por outras opções, neste caso, você já está se beneficiando do ganho de usar o registro EBP / RBP e nenhum ganho adicional será obtido especificando explicitamente essa opção se ela já estiver implicitamente). Observe, no entanto, que nos modos de 16 e 32 bits, o registrador BP não tem a capacidade de acessar partes de 8 bits como o AX (AL e AH).

Como essa opção, além de permitir que o compilador use o EBP como um registrador de propósito geral nas otimizações, também impede a geração de código de input e saída para o quadro de pilha que complica a debugging – é por isso que a documentação do GCC declara explicitamente (enfatizando enfaticamente com um negrito estilo) que habilitar essa opção torna a debugging impossível em algumas máquinas

Por favor, esteja ciente de que outras opções de compilador, relacionadas à debugging ou otimização, podem implicitamente ativar ou desativar a -fomit-frame-pointer .

Eu não encontrei nenhuma informação oficial no gcc.gnu.org sobre como outras opções afetam o -fomit-frame-pointer em plataformas x86 , o https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc /Optimize-Options.html apenas afirma o seguinte:

-O também ativa -fomit-frame-pointer em máquinas onde isso não interfere na debugging.

Portanto, não está claro a partir da documentação em si se -fomit-frame-pointer será ativado se você apenas compilar com uma única opção -O na plataforma x86. Pode ser testado empiricamente, mas neste caso não há compromisso dos desenvolvedores do GCC em não alterar o comportamento desta opção no futuro sem aviso prévio.

No entanto, Peter Cordes salientou nos comentários que há uma diferença para as configurações padrão do -fomit-frame-pointer entre as plataformas x86-16 e as plataformas x86-32 / 64.

Esta opção – -fomit-frame-pointer – também é relevante para o Intel C ++ Compiler 15.0 , não apenas para o GCC:

Para o Intel Compiler, esta opção tem um alias /Oy .

Aqui está o que a Intel escreveu sobre isso:

Essas opções determinam se o EBP é usado como um registrador de uso geral em otimizações. Opções -fomit-frame-pointer e / Oy permitem este uso. Opções -fno-omit-frame-pointer e / Oy- recusam.

Alguns depuradores esperam que o EBP seja usado como um ponteiro de quadro de pilha e não pode produzir um backtrace de pilha, a menos que seja assim. O -fno-omit-frame-pointer e / Oy- options orientam o compilador a gerar código que mantém e usa EBP como um ponteiro de quadro de pilha para todas as funções, de forma que um depurador ainda pode produzir um backtrace de pilha sem fazer o seguinte:

Para -fno-omit-frame-pointer: desativando as otimizações com -O0 Para / Oy-: desligando / O1, / O2 ou / O3 otimizações A opção -fno-omit-frame-pointer é configurada quando você especifica a opção – O0 ou a opção -g. A opção -fomit-frame-pointer é definida quando você especifica a opção -O1, -O2 ou -O3.

A opção / Oy é definida quando você especifica a opção / O1, / O2 ou / O3. Opção / Oy- é definido quando você especifica a opção / Od.

Usar o ponteiro -fno-omit-frame-pointer ou / Oy- reduz o número de registradores de propósito geral disponíveis em 1 e pode resultar em um código um pouco menos eficiente.

NOTA Para sistemas Linux *: Atualmente, há um problema com o tratamento de exceções do GCC 3.2. Portanto, o compilador Intel ignora essa opção quando o GCC 3.2 é instalado para C ++ e o tratamento de exceção está ativado (o padrão).

Por favor, esteja ciente de que a citação acima é relevante somente para o compilador Intel C ++ 15, não para o GCC.