Implement text mapping, compiler optimizations, and benchmarks - #20
Conversation
…clarações lazy - Adiciona passes de otimização LLVM (SROA, EarlyCSE, InstCombine) via parâmetro `otimizar` - Define target triple e data layout da plataforma nativa no módulo - Adiciona atributos `nounwind` e `willreturn` em funções do usuário - Elimina bloco `se_senao` vazio quando não há caminho alternativo - Registra apenas módulos efetivamente importados no código (declarações lazy) - Corrige coerção de literais inteiros em expressões binárias com resultado de subexpressões - Corrige tipo de retorno em funções do usuário via `tipoRetornoFuncaoAtual` - Corrige resolução de argumentos VariavelEscopo em chamadas de função - Adiciona flag `-O2` na compilação e linkagem de bibliotecas nativas - Adiciona benchmark suite (fibonacci recursivo + contagem de primos) comparando com C e Rust
… e atualiza os testes correspondentes
There was a problem hiding this comment.
Pull request overview
Este PR expande o backend LLVM do compilador Delégua com melhorias em vetores (alocação no heap, escrita por índice, métodos mapear/tamanho), adiciona um caminho opcional de otimizações LLVM (-O2/passes) e inclui uma suíte simples de benchmarks (Delégua vs C vs Rust).
Changes:
- Vetores: alocação de elementos via
malloc, suporte a escrita por índice e novos métodos (mapearpara texto etamanho), com novos testes. - Build/CLI: seleção de bibliotecas nativas a compilar/linkar com base nos módulos efetivamente importados + flags de link; clang/clang++ com
-O2. - Benchmarks: programas (fib/primos/bubblesort) em Delégua/C/Rust e script para executar/validar.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| testes/compilador-llvm.vetores.test.ts | Atualiza expectativas de IR para vetores no heap e adiciona testes de escrita/leitura por índice. |
| testes/compilador-llvm.se.test.ts | Atualiza asserts para comparações inteiras e geração condicional do bloco se_senao. |
| testes/compilador-llvm.metodos-vetor.test.ts | Adiciona testes para método tamanho(). |
| testes/compilador-llvm.metodos-vetor-com-funcoes.test.ts | Adiciona testes para mapear como método (inteiro/número/texto) e função nomeada. |
| testes/compilador-llvm.importacao.test.ts | Ajusta teste para cenário com múltiplos imports e tamanho do mapa de módulos. |
| testes/compilador-llvm.funcoes.test.ts | Adiciona teste de recursão (fibonacci). |
| fontes/ilc.ts | Passa otimizar=true para o compilador e compila/linka bibliotecas nativas com -O2 + flags por módulo importado. |
| fontes/compilador-llvm.ts | Implementa mudanças de vetores (heap, cache de ponteiro), novos métodos, registro seletivo de módulos e passes de otimização. |
| fontes/bibliotecas/vetor.h | Declara delegua_vetor_tamanho e delegua_vetor_mapear_texto. |
| fontes/bibliotecas/vetor.c | Implementa delegua_vetor_mapear_texto e delegua_vetor_tamanho. |
| fontes/bibliotecas-compilacao.ts | Novo helper para detectar módulos importados e determinar arquivos C/flags de link necessários. |
| benchmarks/* | Adiciona benchmarks em C/Rust/Delégua + script executar.sh. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| valorFinal = this.montador.CreateLoad(tipoElemento, valorResolvido.variavelLlvm, 'val_store'); | ||
| } else { | ||
| valorFinal = valorResolvido as llvm.Value; | ||
| } | ||
|
|
||
| this.montador.CreateStore(valorFinal, gepElemento); | ||
| return Promise.resolve(valorFinal); |
There was a problem hiding this comment.
Na atribuição por índice em vetor, valorFinal é armazenado diretamente em gepElemento sem coerção para o tipo do elemento. Como literais numéricos frequentemente chegam como double no AST/IR, isso pode gerar store double em um i32* (IR inválido) para inteiro[]. Converta valorFinal para tipoElemento quando necessário (ex.: FPToSI/SIToFP) antes do CreateStore.
| valorFinal = this.montador.CreateLoad(tipoElemento, valorResolvido.variavelLlvm, 'val_store'); | |
| } else { | |
| valorFinal = valorResolvido as llvm.Value; | |
| } | |
| this.montador.CreateStore(valorFinal, gepElemento); | |
| return Promise.resolve(valorFinal); | |
| // Quando o valor é uma variável, carregamos já no tipo do elemento. | |
| valorFinal = this.montador.CreateLoad(tipoElemento, valorResolvido.variavelLlvm, 'val_store'); | |
| } else { | |
| valorFinal = valorResolvido as llvm.Value; | |
| } | |
| // Garante que o tipo do valor a armazenar coincide com o tipo do elemento do vetor. | |
| let valorConvertido: llvm.Value = valorFinal; | |
| const tipoValor = valorFinal.getType(); | |
| if (tipoValor !== tipoElemento) { | |
| // Conversões numéricas comuns: double <-> inteiro. | |
| const ehFloatOrigem = tipoValor.isFloatingPointTy && tipoValor.isFloatingPointTy(); | |
| const ehIntOrigem = tipoValor.isIntegerTy && tipoValor.isIntegerTy(); | |
| const ehFloatDestino = tipoElemento.isFloatingPointTy && tipoElemento.isFloatingPointTy(); | |
| const ehIntDestino = tipoElemento.isIntegerTy && tipoElemento.isIntegerTy(); | |
| if (ehFloatOrigem && ehIntDestino) { | |
| // Ex.: armazenar literal double em vetor de inteiros. | |
| valorConvertido = this.montador.CreateFPToSI(valorFinal, tipoElemento, 'val_store_fptosi'); | |
| } else if (ehIntOrigem && ehFloatDestino) { | |
| // Ex.: armazenar inteiro em vetor de números de ponto flutuante. | |
| valorConvertido = this.montador.CreateSIToFP(valorFinal, tipoElemento, 'val_store_sitofp'); | |
| } else { | |
| // Fallback: tentativa de coerção por bitcast para tipos compatíveis. | |
| valorConvertido = this.montador.CreateBitCast(valorFinal, tipoElemento, 'val_store_bitcast'); | |
| } | |
| } | |
| this.montador.CreateStore(valorConvertido, gepElemento); | |
| return Promise.resolve(valorConvertido); |
| ); | ||
| const ptrElementos = this.montador.CreateLoad(tipoPonteiro, gepCampoPtr, 'ptr_elementos'); | ||
| // Carrega o ponteiro de elementos (com cache para evitar loads redundantes). | ||
| const nomeVetor = (alvoBruto as any)?.simbolo?.lexema ?? ''; |
There was a problem hiding this comment.
O cache de ponteiro de elementos usa nomeVetor como chave, mas quando o acesso não é uma variável simples (alvoBruto sem simbolo.lexema) a chave vira string vazia. Isso pode causar colisões e reutilização do ponteiro errado entre vetores/expressões diferentes no mesmo bloco básico. Sugestão: keyar o cache pelo próprio vetorPtr (Map<llvm.Value, llvm.Value>) ou só usar cache quando houver um identificador estável e único.
| const nomeVetor = (alvoBruto as any)?.simbolo?.lexema ?? ''; | |
| const nomeVetorBase = (alvoBruto as any)?.simbolo?.lexema; | |
| const nomeVetor = | |
| (nomeVetorBase && nomeVetorBase.length > 0) | |
| ? nomeVetorBase | |
| : ((alvoResolvido.variavelLlvm as any)?.getName?.() || | |
| alvoResolvido.variavelLlvm.toString()); |
| // Detecta módulos importados para registrar apenas as funções necessárias. | ||
| const modulosImportados = new Set<string>(); | ||
| const regexImportar = /importar\s*\(\s*['"](\w+)['"]\s*\)/; | ||
| const regexDe = /importar\s+.*\s+de\s+['"](\w+)['"]/; | ||
| for (const linha of codigo) { | ||
| const match = regexImportar.exec(linha) || regexDe.exec(linha); | ||
| if (match) modulosImportados.add(match[1]); | ||
| } |
There was a problem hiding this comment.
A detecção de módulos importados foi reimplementada aqui via regex linha-a-linha, mas já existe detectarModulosImportados() em fontes/bibliotecas-compilacao.ts. Para evitar divergências futuras (regex/sintaxe suportada), considere reutilizar o helper comum ou, idealmente, derivar os imports a partir do AST do avaliador sintático.
| let argumentoResolvido = await argumentoAjustado.aceitar(this); | ||
| if (argumentoResolvido instanceof VariavelEscopo) { | ||
| const tipoParam = tiposParametros[indice] || argumentoResolvido.tipo || 'número'; | ||
| const tipoLlvm = this.obterTipoLlvm(tipoParam); | ||
| argumentoResolvido = this.montador.CreateLoad(tipoLlvm, argumentoResolvido.variavelLlvm, 'load_arg'); | ||
| } |
There was a problem hiding this comment.
Em visitarExpressaoDeChamada, o código faz CreateLoad() para qualquer VariavelEscopo retornada como argumento. Para parâmetros de função (onde VariavelEscopo.variavelLlvm é um llvm.Argument não-ponteiro), isso gera load em um valor não-pointer (IR inválido/erro no binding). Ajuste para só dar load quando o llvm.Value for ponteiro (ex.: usando tipoEhPonteiro(getType())/carregarValorSeNecessario) e, caso contrário, passar o valor direto.
| let argumentoResolvido = await argumentoAjustado.aceitar(this); | ||
| if (argumentoResolvido instanceof VariavelEscopo) { | ||
| const tipoParam = argumentoResolvido.tipo || argumento.tipo || 'número'; | ||
| const tipoLlvm = this.obterTipoLlvm(tipoParam); | ||
| argumentoResolvido = this.montador.CreateLoad(tipoLlvm, argumentoResolvido.variavelLlvm, 'load_arg'); | ||
| } |
There was a problem hiding this comment.
Mesma questão no caminho sem construtoCorrespondente: argumentos que já são valores (ex.: parâmetros de função representados por llvm.Argument) não devem sofrer CreateLoad(). Hoje o bloco sempre faz load se instanceof VariavelEscopo, o que pode quebrar chamadas envolvendo parâmetros/valores agregados. Restrinja o load a casos onde variavelLlvm é ponteiro.
No description provided.