Se você já abriu o código do kernel, sabe que a header include/linux/minmax.h
parece inofensiva: meia dúzia de macros — min()
, max()
, clamp()
— para comparar valores. Só que, ao longo dos anos, essas macros foram crescendo, ganhando verificação de tipos, truques de “const-expr” e camadas de proteção contra armadilhas da linguagem C. Resultado? Um monstro de metaprogramação que explodia o pré-processador em cascata e tornava algumas compilações absurdamente lentas. Este artigo conta como a dupla Linus Torvalds e David Laight liderou uma limpeza profunda e modernização do Linux kernel minmax.h, e por que isso fez tanta diferença — inclusive com um caso real de 16× de speed-up numa linha problemática de driver.
Como chegamos aqui
Para evitar bugs sutis, o kernel fazia as macros min()/max()/clamp()
checarem compatibilidade de tipos (principalmente de sinal, assinado vs. não assinado) e ainda tentava manter o resultado como expressão constante quando possível. Isso parecia prudente — até que o custo se acumulou. Cada macro expandia em ramos de __builtin_choose_expr()
, detecções de signedness, e duplicações de argumentos para evitar avaliar duas vezes (efeitos colaterais). Agora imagine isso dentro de outras macros que também dependem de min()/max()
. A multiplicação explosiva de tokens tornava certas linhas… épicas.
Linus deu um exemplo inesquecível: um arquivo do driver AtomISP tinha uma única linha que, depois das expansões, atingia 23,3 MB de texto de pré-processador (!). Com as mudanças em minmax.h
, essa mesma linha caiu para 1,4 MB, e o tempo de compilação daquela unidade despencou de ~16,6 s para ~2,5 s — um ganho de cerca de 16× no caso extremo. Isso está descrito na própria justificativa de commit da simplificação de min()/max()/clamp()
(e mesmo num backport para estável 5.10, de tão útil que ficou).
A faxina: menos mágica, mais objetividade
O pacote de alterações veio em ondas, com commits assinados por Linus Torvalds e uma série de refinos por David Laight. O fio condutor foi: remover complexidade desnecessária sem perder segurança de tipos onde realmente importa. Os principais pontos:
- Separar casos de “constante de topo”: surgiram macros explícitas
MIN()
/MAX()
(em maiúsculas) eMIN_T()
/MAX_T()
para situações onde queremos avaliar expressões constantes ou inicializadores estáticos sem arrastar toda a parafernália das versões minúsculas. Essas variantes não fazem checagem de tipos e podem avaliar argumentos mais de uma vez — então são para usos óbvios (p. ex., limites fixos). Isso ajuda a evitar que constantes centrais (comopageblock_order
) virem “lama” de 2,5 kB toda vez que aparecem dentro de outra macro. (GitLab) - Simplificar
min_t()/max_t()
: como o tipo já é dado, a macro não precisa da mesma bateria de verificações. Linus cortou o suporte a contextos que exigem strict C constant expressions (array sizes, inicializadores) nessas versões tipadas; quando for realmente necessário ser constante, useMIN_T/MAX_T
. Menos caminhos especiais = menos tokens expandidos. (GitLab) - A grande poda em
min()/max()/clamp()
: o commit “minmax: simplify min()/max()/clamp() implementation” removeu o caminho de “se for constexpr, faz X; senão, Y”, e passou a sempre usar temporários do tipo certo, com checagem de signedness mais direta. Isso corta toneladas de__builtin_choose_expr()
aninhados e diminui radicalmente o tamanho das expansões — é aqui que mora o salto de 23 MB → 1,4 MB e o speed-up de 16× naquele driver da Intel. - Checagem de tipos mais esperta (e barata): a verificação de “podemos comparar esses dois valores sem surpresas?” ficou mais clara e menos inflada. Ex.: inteiros assinados estaticamente não negativos podem participar de comparações sem carregar a paranoia toda; tipos não assinados pequenos (promovidos a
int
) continuam aceitos por compatibilidade. Menos macro-ginástica, mesma segurança prática. min3()/max3()
corrigidos: as versões “de três” antes compunhammin(min(x,y),z)
com casts detypeof(x)
, o que podia gerar resultados incorretos em combinações de tamanhos e sinais (além de expandir demais). Agora, usam a mesma infraestrutura “careful” de comparação com temporários e checagem única. Adeus armadilhas.clamp()
e família: a família toda foi reorganizada (definições agrupadas commin/max
) e simplificada. A sanidadelo ≤ hi
virou umBUILD_BUG_ON_MSG(statically_true(ulo > uhi))
, cobrindo melhor os casos e evitando outra rodada de expansão, e as variantesclamp_t()
/clamp_val()
foram alinhadas para reaproveitar o mesmo núcleo enxuto. Há até limpeza cosmética (espaços, comentários) — menos ruído, mais leitura.
Efeito dominó no kernel (e nos estáveis)
Não é “apenas” academia de macros. As simplificações liberaram vários trechos do kernel de gambiarras locais: drivers que definiam seus próprios MIN/MAX
, usos de max()
em locais que precisavam de constante (trocados por MAX()
), buffers dimensionados com max_t()
convertidos para MAX_T()
, e por aí vai. O resultado é um ecossistema mais consistente: menos duplicata, menos colisão de nomes, menos expansão redundante.
Por ser um ganho concreto de tempo de compilação (e de saúde mental para quem depura pré-processamento), a série começou a ser levada para ramos estáveis. Há evidências tanto nos dashboards que rastreiam commits quanto em listas que carregam backports (o texto do commit com os números de 23 MB → 1,4 MB aparece inclusive numa série de 5.10.y). Em resumo: não é só mainline que se beneficia; LTS também.
Por que isso importa (além da velocidade)
O Linux kernel minmax.h é um daqueles alicerces invisíveis. Ele aparece em todo lugar — do VM ao DRM — e qualquer microcusto nele multiplica. Reduzir 10×, 15×, 16× o tamanho de certas expansões significa:
- Builds mais rápidas e previsíveis, especialmente em módulos “macro-pesados”.
- Logs de pré-processamento legíveis quando você realmente precisa investigar por que algo virou espaguete.
- Menos risco de cair em cantos obscuros de promoção implícita de tipos do C — a checagem ficou mais clara sobre o que é permitido (e por quê).
- Código do kernel mais limpo: standardizar
MIN/MAX
em maiúsculas para constantes emin/max
para o dia a dia reduz “dialetos locais”.
E, num nível cultural, é um lembrete do estilo Torvalds: descobrir um gargalo “besta”, entender o porquê, simplificar — e aceitar um pouco menos de magia quando a simplicidade entrega 99% do benefício sem pedágios.
No fim, o que mudou? Não a funcionalidade — min
, max
, clamp
continuam fazendo o óbvio —, mas como chegam lá: com menos alquimia macro e mais eficiência prática. E isso, num projeto do tamanho do kernel, faz toda a diferença.