Baseado em: análise do PerryTS + estado atual do RTS + Cranelift 0.131 API
Objetivo: separar o monólito src/ em crates independentes, introduzir HIR e
MIR como camadas explícitas entre parser e codegen, manter ABI extern "C" intacta,
e usar o repertório completo do Cranelift corretamente em cada camada.
A Fase 3 (MIR) está completa e em produção — rts-mir ativo por
default desde os commits f7b924b (etapa 3.25) e 23dd4b7 (etapa
3.26).
Pipeline atual:
TS → SWC → AST → HIR (rts-hir) → MIR (rts-mir) → optimize (fold+dce+narrow+verify)
→ mir_codegen → Cranelift → JIT/AOT
↘ AST autoritativo (fallback automatico)
Routing hibrido controlado por RTS_USE_MIR:
| Valor | Comportamento |
|---|---|
unset / 1 / on / all |
MIR ON (default) |
0 / off / none |
AST only |
fn1,fn2,... |
MIR só pras fns listadas |
Cada user fn tenta o caminho HIR→MIR→Cranelift; se bate em construct
ainda não modelado (member em this/objetos, classes, async/await,
address-taken fns, string em params/ret de user fn), cai automatica
e silenciosamente no codegen AST sem perder semântica.
Marcos relevantes (commits na main):
35a8ed3craterts-mirskeleton48e1f0dlower HIR → MIR linear96e10c4passes fold + dce9507b09passes verify + narrowbdf25c0mir_codegenMIR → Cranelift IR0629de8CallUser cross-fn calls7f59338CallExtern para namespaces RTS3b4a22aStrLit + StrPtr expansion (io.print, io.stdout_write)1ad4d52namespace constants (math.PI)8d01fb6Intrinsic inlining (sqrt nativo)9544104arrays simples via collections.vec_*de3a721GC stack maps no caminho MIR7e8451arouting híbrido MIR ↔ AST com opt-ine8dc4a7mutation SSA (block params paralet i = i + 1)f3e45abgate conservador roda suite full31d8796zero regressão com RTS_USE_MIR=allf7b924bMIR ATIVADO POR DEFAULT (etapa 3.25)23dd4b7cobertura MIR expandida 25 → 438 fns reais (etapa 3.26)
Métricas atuais:
cargo test --release --lib: 12/12cargo test -p rts-hir: 27/27cargo test -p rts-mir: 51/51cargo test -p rts-codegen --lib mir_codegen: 53/53target/release/rts.exe test: 622/632 (mesmas 10 falhas pré-existentes)- 438 user fns reais da suite TS rodam pelo MIR (cobertura ~3% das ~14000 fns user; 73% bail por synthetic/test-framework, 13% por placeholders, 12% por tipos)
Workspace de crates atualmente (10 crates):
rts-ast, rts-parser, rts-diagnostics, rts-abi, rts-hir,
rts-mir, rts-runtime, rts-codegen (com mir_codegen/),
rts-linker, rts-cli.
Capacidades MIR: literais, aritmética inteira/float, bitwise,
shifts, comparações, casts; control flow completo (if/else, while,
do-while, for clássico, break/continue, ternary→Select, switch via
br_table, throw→Trap, try/finally fase 1); mutação SSA via block
params; cross-fn calls (CallUser) + recursão mútua; auto-recursão
bail (TCO ainda só no AST); CallExtern para namespaces RTS;
Intrinsic inlining; StrLit + StrPtr expansion; namespace constants
(math.PI/E); arrays simples via collections.vec_*; GC stack maps
automáticos.
Fora do escopo MIR ainda (rota AST): member access em this/objetos; classes (new, métodos, herança, this binding); async/await + Promise; address-taken fns (passadas a thread.spawn, promise.then, etc.); string em params/ret de user fn (StrPtr no boundary).
O plano original (abaixo) permanece como referência da arquitetura alvo. Fases 0–3 entregues; Fase 4 (baixo nível e extensões) em progresso — 5/8 itens entregues.
Em progresso na main. Commits relevantes:
aa334a7— etapa 4.1 atomics no mir_codegen:Inst::AtomicLoad/AtomicStore/AtomicRmw/AtomicCas/Fencebaixam direto paraatomic_load/atomic_store/atomic_rmw/atomic_cas/fencedo Cranelift, com mapeamentoMemOrdereRmwOp.8d4acd4— etapa 4.2 passes/inline.rs: inlining de fns pequenas comINLINE_BUDGET=16e elegibilidade conservadora (sem recursão, sem terminators complexos).9c529a9— etapa 4.3 inline integrado: routing default alimentaMIR_CACHEthread-local com callees + pré-registra signatures HIR para o pass de inline encontrar os corpos.815e1df— etapa 4.4 smoke test e2e: helpercompile_source_via_compile_programcobre o caminhoparse → HIR → MIR → optimize → mir_codegen → JITponta a ponta.5a63892— etapa 4.5 passes/cse.rs: Common Subexpression Elimination intra-bloco (hash de operandos+op, reusa ValueId quando idêntico encontrado no bloco).2236d91— etapa 4.6 arr[i] = v: assignment de índice viavec_set+ helpermark_placeholdertaggeado (RTS_MIR_DEBUG_PLACEHOLDERS=1imprime sites onde o lower emitiu placeholder).9e96aff— etapa 4.7 inline em fixed-point: até 4 iterações comoptimizeentre passadas, permitindo cascade de inlining + fold + dce + cse.e79b8bf— etapa 4.8 passes/fma.rs: FMA fusiona*b+c → Fma, conservador (só funde quando oFMultem 1 use) para não duplicar trabalho.326a7ae— TailCall lowering em mir_codegen:Terminator::TailCallemitebuilder.ins().return_call(fref, args)direto (infra preparada;lowerHIR→MIR ainda não emite TailCall, auto-recursão bail antes).91517d9— etapa 4.9 narrow no optimize(): passnarrowpassa a rodar como primeira etapa dooptimize()padrão. Aritmética i8/i16/u8/u16 que passe pelo MIR ganha mask automático sem intervenção manual.
narrow (canonicaliza aritmética i8/i16/u8/u16 com masks de overflow)
→ fold (constant folding + strength reduction)
→ fma (a*b+c → Fma)
→ cse (common subexpression elimination)
→ dce (dead code elimination)
Mais inline em fixed-point (max 4 iters) entre lower e
optimize no try_compile_via_mir.
cargo test --release --lib: 12/12cargo test -p rts-hir: 27/27cargo test -p rts-mir: 59/59 (era 51 ao final da Fase 3 — +8 testes pra inline/CSE/FMA)cargo test -p rts-codegen --lib mir_codegen: 61/61 (era 53 ao final da Fase 3)target/release/rts.exe test: 622/632 (zero regressão, mesmas 10 falhas pré-existentes)
- Atomics no MIR + mir_codegen (etapa 4.1)
- Inlining de fns pequenas (etapa 4.2) — com extras: integração routing default (4.3), fixed-point (4.7)
- CSE intra-bloco (etapa 4.5)
- FMA fusion
a*b+c → Fma(etapa 4.8) - Smoke e2e + placeholder debug + arr[i]=v (4.4 / 4.6)
- Narrow canonicalization integrada ao optimize() — pass
narrowagora roda como primeira etapa do pipeline padrão (commit91517d9) e é idempotente (commitee4a539) — chamadas em fixed-point não acumulam masks. Permite uso com inline. - SIMD V128 minimo —
HirType::V128(VecKind)(I8x16/I16x8/I32x4/I64x2/F32x4/F64x2) +Inst::VSplat/VExtractLane/VInsertLane/VAdd/VSub/VMulbaixados direto pra ops vetoriais nativas Cranelift (commit4846f93). Falta exposicao TS-side (builtinrts:simd) — follow-up. - ValTy I8/I16/U8/U16 + i8/i16/u8/u16 first-class TS — variantes ValTy adicionadas (
ee4a539),from_annotationreconhece,mir_param_compatiblerota narrow pelo MIR,hir_to_clmapeia narrow→I64 evitando UB em shifts, novo passnarrow_returninsere sign-extend no boundary signed (commitffa3af2). Validado end-to-end:addI8(127,1) = -128,addU8(200,100) = 44,mulI16(32767,2) = -2. - [~] Escape analysis: won't-fix no design atual. Handles RTS são u64 opacos via
HandleTableglobal (slab shard-aware). "Escape" é por construção: qualquer alocação já entra na tabela global, e StackAlloc não substitui esse backend sem reescrever a ABI extern "C" + 25+ variantes deEntry. Ganho real só viria com migração para gc-arena (issue #393, adiada por incompatibilidade comextern "C"plano). Reabrir quando/se a migração acontecer.
| Problema atual | Consequência |
|---|---|
codegen/lower/func.rs com 9 k linhas |
revisão difícil, build lento, testes acoplados |
| Parser → Cranelift direto (sem IR) | otimizações de alto nível impossíveis — Cranelift não faz inlining, DCE, escape analysis, constant folding interprocessual |
AbiType acoplada a cranelift_codegen::ir::Type em rts-abi |
qualquer crate que precise dos contratos ABI puxa cranelift como dep |
| Todos os 40+ namespaces no mesmo crate | recompilar tudo ao mexer em qualquer namespace |
type_system/ com 4 arquivos (~500 linhas) |
type refine agressivo inviável — emite any-path com coerções em hot loops |
CallConv::Tail não usado em todos os lugares certos |
tail calls não otimizados em casos que poderiam ser |
| Narrow types (i8, i16, i32) sem suporte de primeira classe no TS | blocos de baixo nível (WASM, FFI, buffers) precisam de bitmasking manual hoje |
Perry resolveu isso com 30+ crates. RTS não precisa chegar lá, mas ~10 crates resolve 80% dos problemas sem overhead de manutenção.
rts/ (workspace root — Cargo.toml de workspace)
crates/
rts-ast/ — AST público (hoje src/parser/ast.rs)
rts-parser/ — SWC wrapper + lowering pra AST (hoje src/parser/)
rts-hir/ — HIR tipado: Expr/Stmt/Type + analysis + lowering de AST
rts-mir/ — MIR SSA-like: blocos básicos + passes de otimização
rts-abi/ — contrato ABI (hoje src/abi/) — ZERO deps Cranelift
rts-codegen/ — Cranelift backend: consome MIR, emite Cranelift IR
rts-runtime/ — todos os namespaces (hoje src/namespaces/)
rts-linker/ — linker wrapper (hoje src/linker/)
rts-diagnostics/ — erros estruturados (hoje src/diagnostics/)
rts-cli/ — CLI + pipeline (hoje src/cli/ + src/pipeline.rs)
src/
main.rs — entrypoint (apenas `rts_cli::main()`)
rts-diagnostics (sem deps RTS)
rts-ast (sem deps RTS)
↑
rts-parser (swc_*, rts-ast, rts-diagnostics)
↑
rts-hir (rts-ast, rts-abi, rts-diagnostics)
↑
rts-mir (rts-hir, rts-abi)
↑
rts-codegen (rts-mir, rts-abi, cranelift-*) ← ÚNICA crate que depende de cranelift
↑
rts-cli (rts-codegen, rts-parser, rts-linker, rts-runtime, rts-abi)
↑
rts (bin) (rts-cli)
rts-abi (sem deps cranelift — só tipos e símbolos próprios)
rts-runtime (rts-abi, tokio, rayon, rustls, ...)
rts-linker (rts-abi, object, target-lexicon)
Invariante crítica: somente rts-codegen importa cranelift-*. Qualquer
use cranelift_codegen fora desse crate é um erro de arquitetura.
AST pura sem lógica de parse ou codegen.
Mover de: src/parser/ast.rs
rts-ast/src/
lib.rs
expr.rs — Expr, Lit, BinOp, UnOp
stmt.rs — Stmt, Decl
item.rs — Item::Function, Item::Class, Item::Import, ...
types.rs — TsType, TsAnnotation (com suporte a i8/i16/i32/i64/u8/u16/u32/u64/f32/f64)
span.rs — SourceSpan, SourceFile
O TsType precisa cobrir todos os inteiros que o Cranelift suporta nativamente:
pub enum TsPrimitive {
// JS/TS nativos
Number, // f64 semântico JS
Boolean,
String,
// Extensões de baixo nível (para FFI, buffers, WASM)
I8, I16, I32, I64, I128,
U8, U16, U32, U64, U128,
F32, F64,
// Void e never
Void,
Never,
}Cranelift tem suporte nativo para todos esses via types::I8 até types::I128,
types::F32, types::F64 — mapeamento direto sem overhead.
Deps: apenas serde (feature flag).
SWC wrapper + lowering para rts-ast. Hoje em src/parser/.
Mover de: src/parser/*.rs
rts-parser/src/
lib.rs
parse_api.rs — entrada pública: parse_file / parse_str
lowering_items.rs — SWC AST → rts-ast Items
lowering_decls.rs — declarações
lowering_helpers.rs — helpers
generator_desugar.rs — desugar generators
passes/
expand_async.rs — expand_async_functions
purity_pass.rs — purity_pass silent parallelism
reduce_pass.rs — reduce_pass
array_methods.rs — array_methods_pass
Os 3 passes de silent parallelism operam sobre AST antes do HIR — sem deps Cranelift.
IR tipado entre AST e codegen. Cada nó HIR carrega seu HirType — resolve
ambiguidade entre number JS (f64) e inteiros nativos, eliminando any-paths
em hot loops.
Criar do zero.
rts-hir/src/
lib.rs
ir.rs — HirExpr, HirStmt, HirFunc, HirType, HirLit
lower.rs — rts-ast → HIR
type_refine.rs — refine_type_from_init
type_map.rs — HirType → CraneliftTypeHint (usado por rts-codegen)
analysis.rs — análise de escopo, resolução de nomes
walker.rs — visitor sobre HIR
/// Tipo de valor no HIR. Mapeamento com Cranelift em type_map.rs.
pub enum HirType {
// JS/TS nativos
Number, // f64 — semântica JS
Bool, // i64 no ABI (0/1); i8 internamente em icmp
Str, // StrPtr (ptr: i64 + len: i64)
Void,
// Inteiros explícitos (baixo nível / FFI)
I8, I16, I32, I64, I128,
U8, U16, U32, U64, U128,
// Floats explícitos
F32, F64,
// Compostos
Handle(HandleKind), // u64 opaco — HandleTable
Array(Box<HirType>),
Function { params: Vec<HirType>, ret: Box<HirType> },
Class(ClassId),
// Fallback
Any, // gatilha guards ABI em call sites
Unknown, // pré-refine; erro se chegar ao codegen
}Este módulo exporta um enum leve (CraneliftTypeHint) que rts-codegen converte
em cranelift_codegen::ir::Type. Isso mantém rts-hir sem dep de cranelift
enquanto o codegen ainda tem informação de tipo rica:
/// Hint de tipo para codegen — não usa tipos cranelift diretamente.
pub enum CraneliftTypeHint {
I8, I16, I32, I64, I128,
F32, F64,
Ptr, // pointer width (I32 ou I64 conforme alvo)
Void,
}
impl HirType {
pub fn cranelift_hint(&self) -> Option<CraneliftTypeHint> {
match self {
HirType::I8 => Some(CraneliftTypeHint::I8),
HirType::I16 => Some(CraneliftTypeHint::I16),
HirType::I32 | HirType::U32 => Some(CraneliftTypeHint::I32),
HirType::I64 | HirType::U64
| HirType::Number // f64 bits viajam como i64 no ABI
| HirType::Bool
| HirType::Handle(_) => Some(CraneliftTypeHint::I64),
HirType::I128 | HirType::U128 => Some(CraneliftTypeHint::I128),
HirType::F32 => Some(CraneliftTypeHint::F32),
HirType::F64 => Some(CraneliftTypeHint::F64),
HirType::Void => Some(CraneliftTypeHint::Void),
HirType::Str => None, // dois slots: ptr + len
HirType::Any
| HirType::Unknown => None, // resolve em runtime
_ => None,
}
}
}pub fn refine_type_from_init(expr: &HirExpr, scope: &Scope) -> HirType {
match expr {
// Literais
HirExpr::Lit(HirLit::Int(_)) => HirType::I64, // inteiro sem anotação → i64
HirExpr::Lit(HirLit::Float(_)) => HirType::F64, // float sem anotação → f64
HirExpr::Lit(HirLit::Number(_)) => HirType::Number, // JS number → f64 semântico
HirExpr::Lit(HirLit::Bool(_)) => HirType::Bool,
HirExpr::Lit(HirLit::Str(_)) => HirType::Str,
// Aritmética numérica — propaga tipo se ambos os lados são numéricos
HirExpr::Bin { op, lhs, rhs } if op.is_arithmetic() => {
let lt = refine_type_from_init(lhs, scope);
let rt = refine_type_from_init(rhs, scope);
numeric_promotion(lt, rt) // ex: i32 op f64 → f64
}
// Arrays e coleções
HirExpr::Array(_) => HirType::Array(Box::new(HirType::Any)),
HirExpr::New { class, .. } if class == "Array" =>
HirType::Array(Box::new(HirType::Any)),
HirExpr::New { class, .. } =>
scope.resolve_class(class)
.map(HirType::Class)
.unwrap_or(HirType::Unknown),
// Calls — usar tipo de retorno da assinatura conhecida
HirExpr::Call { callee, .. } =>
scope.return_type_of(callee).unwrap_or(HirType::Unknown),
// Cast explícito de tipo primitivo
HirExpr::Cast { target, .. } => target.clone(),
_ => HirType::Unknown,
}
}
/// Promoção numérica entre dois tipos (segue regras C/Rust)
fn numeric_promotion(a: HirType, b: HirType) -> HirType {
use HirType::*;
match (a, b) {
(F64, _) | (_, F64) => F64,
(F32, _) | (_, F32) => F32,
(I128, _) | (_, I128)
| (U128, _) | (_, U128) => I128,
(I64, _) | (_, I64)
| (U64, _) | (_, U64) => I64,
(I32, _) | (_, I32) => I32,
(I16, _) | (_, I16) => I16,
(I8, _) | (_, I8) => I8,
(Number, _) | (_, Number) => Number,
_ => I64, // fallback seguro
}
}Impacto: let i = 0; for(...) i += 1 hoje emite any-path com coerções.
Com refine, i vira I64 e o codegen emite iadd_imm direto — sem call extern.
MIR SSA-like — blocos básicos com block parameters (= phis), operações tipadas.
Espelha o modelo do Cranelift (Block + params + Terminator) para que a
tradução MIR → Cranelift IR seja mecânica e sem surpresas.
Criar do zero.
rts-mir/src/
lib.rs
ir.rs — MirFunc, BasicBlock, Inst, Value, Terminator
lower.rs — HIR → MIR
passes/
dce.rs — dead code elimination
fold.rs — constant folding + strength reduction
inline.rs — inlining de fns pequenas (future)
escape.rs — escape analysis para handles (future)
narrow.rs — canonicalização de narrow types (i8/i16/i32 ops)
verify.rs — invariantes MIR (debug build)
pub type ValueId = u32;
pub type BlockId = u32;
pub struct MirFunc {
pub name: String,
pub conv: CallConvHint, // Tail | SystemV | WindowsFastcall
pub params: Vec<(ValueId, HirType)>,
pub ret: HirType,
pub blocks: Vec<BasicBlock>,
pub values: Vec<HirType>, // type table indexed by ValueId
}
pub struct BasicBlock {
pub id: BlockId,
pub params: Vec<(ValueId, HirType)>, // block params = SSA phis
pub insts: Vec<Inst>,
pub term: Terminator,
}
pub enum Inst {
// Arithmetic
IAdd { dst: ValueId, lhs: ValueId, rhs: ValueId },
IAddImm { dst: ValueId, lhs: ValueId, imm: i64 },
ISub { dst: ValueId, lhs: ValueId, rhs: ValueId },
IMul { dst: ValueId, lhs: ValueId, rhs: ValueId },
IMulImm { dst: ValueId, lhs: ValueId, imm: i64 },
SDiv { dst: ValueId, lhs: ValueId, rhs: ValueId },
UDiv { dst: ValueId, lhs: ValueId, rhs: ValueId },
SRem { dst: ValueId, lhs: ValueId, rhs: ValueId },
URem { dst: ValueId, lhs: ValueId, rhs: ValueId },
INeg { dst: ValueId, src: ValueId },
// Bitwise
BAnd { dst: ValueId, lhs: ValueId, rhs: ValueId },
BAndImm { dst: ValueId, lhs: ValueId, imm: i64 },
BOr { dst: ValueId, lhs: ValueId, rhs: ValueId },
BOrImm { dst: ValueId, lhs: ValueId, imm: i64 },
BXor { dst: ValueId, lhs: ValueId, rhs: ValueId },
BXorImm { dst: ValueId, lhs: ValueId, imm: i64 },
BNot { dst: ValueId, src: ValueId },
// Shifts
IShl { dst: ValueId, lhs: ValueId, rhs: ValueId },
IShlImm { dst: ValueId, lhs: ValueId, imm: i64 },
UShr { dst: ValueId, lhs: ValueId, rhs: ValueId },
SShr { dst: ValueId, lhs: ValueId, rhs: ValueId },
SShrImm { dst: ValueId, lhs: ValueId, imm: i64 },
Rotl { dst: ValueId, lhs: ValueId, rhs: ValueId },
Rotr { dst: ValueId, lhs: ValueId, rhs: ValueId },
// Bit counting
Clz { dst: ValueId, src: ValueId },
Ctz { dst: ValueId, src: ValueId },
Popcnt { dst: ValueId, src: ValueId },
Bswap { dst: ValueId, src: ValueId },
// Float arithmetic
FAdd { dst: ValueId, lhs: ValueId, rhs: ValueId },
FSub { dst: ValueId, lhs: ValueId, rhs: ValueId },
FMul { dst: ValueId, lhs: ValueId, rhs: ValueId },
FDiv { dst: ValueId, lhs: ValueId, rhs: ValueId },
FNeg { dst: ValueId, src: ValueId },
FAbs { dst: ValueId, src: ValueId },
Sqrt { dst: ValueId, src: ValueId },
Fma { dst: ValueId, a: ValueId, b: ValueId, c: ValueId },
FMin { dst: ValueId, lhs: ValueId, rhs: ValueId },
FMax { dst: ValueId, lhs: ValueId, rhs: ValueId },
Ceil { dst: ValueId, src: ValueId },
Floor { dst: ValueId, src: ValueId },
Trunc { dst: ValueId, src: ValueId },
// Conversions
IReduce { dst: ValueId, src: ValueId, to: HirType }, // truncate
UExtend { dst: ValueId, src: ValueId, to: HirType }, // zero-extend
SExtend { dst: ValueId, src: ValueId, to: HirType }, // sign-extend
FPromote { dst: ValueId, src: ValueId }, // f32 → f64
FDemote { dst: ValueId, src: ValueId }, // f64 → f32
CvtFromSint { dst: ValueId, src: ValueId, to: HirType }, // int → float
CvtFromUint { dst: ValueId, src: ValueId, to: HirType },
CvtToSintSat { dst: ValueId, src: ValueId, to: HirType }, // float → int (sat)
CvtToUintSat { dst: ValueId, src: ValueId, to: HirType },
Bitcast { dst: ValueId, src: ValueId, to: HirType }, // reinterpret bits
// Comparisons
ICmp { dst: ValueId, cond: IntCond, lhs: ValueId, rhs: ValueId },
FCmp { dst: ValueId, cond: FloatCond, lhs: ValueId, rhs: ValueId },
// Constants
IConst { dst: ValueId, ty: HirType, val: i64 },
F32Const { dst: ValueId, val: f32 },
F64Const { dst: ValueId, val: f64 },
// Memory
Load { dst: ValueId, ty: HirType, ptr: ValueId, offset: i32, flags: MemHint },
Store { val: ValueId, ptr: ValueId, offset: i32, flags: MemHint },
// Narrow loads
ULoad8 { dst: ValueId, ptr: ValueId, offset: i32 },
ULoad16 { dst: ValueId, ptr: ValueId, offset: i32 },
ULoad32 { dst: ValueId, ptr: ValueId, offset: i32 },
SLoad8 { dst: ValueId, ptr: ValueId, offset: i32 },
SLoad16 { dst: ValueId, ptr: ValueId, offset: i32 },
SLoad32 { dst: ValueId, ptr: ValueId, offset: i32 },
// Narrow stores
IStore8 { val: ValueId, ptr: ValueId, offset: i32 },
IStore16 { val: ValueId, ptr: ValueId, offset: i32 },
IStore32 { val: ValueId, ptr: ValueId, offset: i32 },
// Stack
StackAlloc { dst: ValueId, size: u32, align: u8 }, // → stack slot addr
// Branchless select
Select { dst: ValueId, cond: ValueId, on_true: ValueId, on_false: ValueId },
// Extern call (runtime ABI)
CallExtern { dst: Option<ValueId>, sym: String, args: Vec<ValueId> },
// Atomics
AtomicLoad { dst: ValueId, ptr: ValueId, order: MemOrder },
AtomicStore { val: ValueId, ptr: ValueId, order: MemOrder },
AtomicRmw { dst: ValueId, op: RmwOp, ptr: ValueId, val: ValueId, order: MemOrder },
AtomicCas { dst: ValueId, ptr: ValueId, expected: ValueId, replacement: ValueId, order: MemOrder },
Fence { order: MemOrder },
// GC
DeclareGcValue { val: ValueId }, // marks value as needing stack map entry
}
pub enum Terminator {
Return(Vec<ValueId>),
Jump { target: BlockId, args: Vec<ValueId> },
Brif { cond: ValueId, then_block: BlockId, then_args: Vec<ValueId>,
else_block: BlockId, else_args: Vec<ValueId> },
Switch { index: ValueId, default: BlockId, cases: Vec<(u64, BlockId)> },
TailCall { sym: String, args: Vec<ValueId> },
TailCallIndirect { fn_ptr: ValueId, args: Vec<ValueId> },
Trap { code: TrapHint },
}
/// Condições inteiras — espelha IntCC do Cranelift exatamente
pub enum IntCond {
Eq, Ne,
Slt, Sle, Sgt, Sge, // signed
Ult, Ule, Ugt, Uge, // unsigned
}
/// Condições float — espelha FloatCC do Cranelift
pub enum FloatCond {
Eq, Ne,
OLt, OLe, OGt, OGe, ONe, // ordered (false if NaN)
ULt, ULe, UGt, UGe, UNe, // unordered (true if NaN)
Ordered, Unordered, // NaN check
}
pub enum MemHint { Trusted, Notrap, Default }
pub enum MemOrder { Relaxed, Acquire, Release, AcqRel, SeqCst }
pub enum RmwOp { Add, Sub, And, Or, Xor, Nand, Xchg, Umin, Umax, Smin, Smax }
pub enum TrapHint { DivByZero, IntOverflow, HeapOob, StackOverflow, User(u16) }
pub enum CallConvHint { Tail, SystemV, WindowsFastcall, Cold }MIR é isomórfico ao Cranelift IR — cada Inst mapeia para exatamente uma
família de instruções Cranelift. Isso torna a tradução em rts-codegen mecânica
e testável independentemente do backend.
Passes que o Cranelift não faz automaticamente (exceto com use_egraphs, que
é intraprocessual). O MIR faz interprocessual:
// Multiplicação por potência de 2 → shift
IConst { val: imm, .. } if is_power_of_two(imm as u64) =>
IMulImm → IShlImm(lhs, imm.trailing_zeros() as i64)
// Módulo por potência de 2 → máscara (unsigned)
IConst { val: imm, .. } if is_power_of_two(imm as u64) =>
URem → BAndImm(lhs, imm - 1)
// Divisão por potência de 2 → shift aritmético (signed)
IConst { val: imm, .. } if is_power_of_two(imm as u64) =>
SDiv → SShrImm(lhs, imm.trailing_zeros() as i64)
// Constant folding puro
IAdd(IConst(a), IConst(b)) → IConst(a + b)
IMul(IConst(a), IConst(b)) → IConst(a * b)
// etc.Operações em i8/i16/i32 precisam de tratamento especial porque Cranelift não tem aritmética nativa de 8/16 bits — opera em i32/i64 com masks:
// Após operação em I8: resultado mascarado pra 8 bits
IAdd { ty: I8, .. } =>
IAdd(lhs, rhs) seguido de BAndImm(result, 0xFF)
// Leitura de i8 de memória: uload8 + zext (já feito automaticamente)
// Escrita de i8: ireduce(I8, val) implícito em istore8
// Promoção para aritmética: I8 operand → sextend I64 antes de IAdd
// Resultado: ireduce I8 depoisIsso garante semântica correta de overflow para tipos menores sem surpresas.
Extração de src/abi/ sem qualquer dep de cranelift. Zero cranelift-* no
Cargo.toml deste crate.
Mover de: src/abi/
rts-abi/src/
lib.rs
member.rs — NamespaceSpec, NamespaceMember, Intrinsic, MemberKind
types.rs — AbiType (own enum, não usa cranelift ir::Type)
signature.rs — LoweredSignature (structs próprias, sem cranelift)
symbols.rs — convenção __RTS_* + rts_sym! macro
guards.rs — guard_for()
global_class.rs — GlobalClassSpec, GLOBAL_CLASS_SPECS
handles.rs — HandleTable ABI constants
mod.rs — SPECS + lookup() + global_class_lookup()
/// Tipo ABI RTS — sem dep de cranelift.
/// Conversão para cranelift::ir::Type fica em rts-codegen/src/abi_bridge.rs
pub enum AbiType {
Void,
Bool, // i64 no boundary extern "C" (0/1)
I8, I16, I32, I64, I128,
U8, U16, U32, U64,
F32, F64,
StrPtr, // 2 slots: (ptr: i64, len: i64)
Handle, // u64 opaco — HandleTable
}pub struct LoweredSignature {
pub symbol: &'static str,
pub params: &'static [AbiType],
pub returns: AbiType,
pub conv: AbiCallConv,
}
pub enum AbiCallConv { Tail, SystemV, WindowsFastcall, Cold }A conversão LoweredSignature → cranelift::ir::Signature fica em
rts-codegen/src/abi_bridge.rs — único lugar que conhece os dois mundos.
Backend Cranelift. Único crate que importa cranelift-*.
Consome MirFunc e emite Cranelift IR via FunctionBuilder.
Mover de: src/codegen/
rts-codegen/src/
lib.rs
setup.rs — ISA + settings (opt_level, preserve_frame_pointers, use_egraphs)
jit.rs — JITModule emitter + register_all_symbols
emit.rs — ObjectModule emitter (AOT)
abi_bridge.rs — AbiType/LoweredSignature → cranelift ir::Type / ir::Signature
lower/
func.rs — MirFunc → Cranelift function (instancia FunctionBuilder)
inst.rs — Inst → builder.ins().* (tradução mecânica 1:1)
term.rs — Terminator → jump/brif/return/return_call
ctx.rs — FnCtx: builder + module + symbol cache
intrinsics.rs — Intrinsic → Cranelift IR inline (sqrt, abs, min, max, etc.)
gc.rs — declare_value_needs_stack_map + stack map registry
pub fn build_isa(opt: OptLevel) -> Arc<dyn TargetIsa> {
let mut b = settings::builder();
// Otimizador principal — e-graphs fazem constant folding, CSE, identidades
b.set("use_egraphs", "true").unwrap();
b.set("enable_alias_analysis", "true").unwrap();
b.set("enable_jump_tables", "true").unwrap();
match opt {
OptLevel::None => b.set("opt_level", "none").unwrap(),
OptLevel::Speed => b.set("opt_level", "speed").unwrap(),
OptLevel::Size => b.set("opt_level", "speed_and_size").unwrap(),
}
// Obrigatório para tail calls em x86-64 (return_call usa frame pointer)
b.set("preserve_frame_pointers", "true").unwrap();
cranelift_native::builder()
.unwrap()
.finish(settings::Flags::new(b))
.unwrap()
}use cranelift_codegen::ir::{types, Type, AbiParam, Signature};
use cranelift_codegen::isa::CallConv;
use rts_abi::{AbiType, AbiCallConv, LoweredSignature};
pub fn abi_type_to_cl(ty: &AbiType, ptr_ty: Type) -> Vec<Type> {
match ty {
AbiType::Void => vec![],
AbiType::Bool => vec![types::I64], // 0/1 em i64 no boundary
AbiType::I8 => vec![types::I8],
AbiType::I16 => vec![types::I16],
AbiType::I32 => vec![types::I32],
AbiType::I64 => vec![types::I64],
AbiType::I128 => vec![types::I128],
AbiType::U8 => vec![types::I8], // sem unsig nado no CL — instrução decide
AbiType::U16 => vec![types::I16],
AbiType::U32 => vec![types::I32],
AbiType::U64 => vec![types::I64],
AbiType::F32 => vec![types::F32],
AbiType::F64 => vec![types::F64],
AbiType::StrPtr => vec![types::I64, types::I64], // ptr + len
AbiType::Handle => vec![types::I64], // u64 como i64
}
}
pub fn lowered_to_cl_sig(sig: &LoweredSignature, ptr_ty: Type) -> Signature {
let conv = match sig.conv {
AbiCallConv::Tail => CallConv::Tail,
AbiCallConv::SystemV => CallConv::SystemV,
AbiCallConv::WindowsFastcall => CallConv::WindowsFastcall,
AbiCallConv::Cold => CallConv::Cold,
};
let mut cl_sig = Signature::new(conv);
for p in sig.params {
for ty in abi_type_to_cl(p, ptr_ty) {
cl_sig.params.push(AbiParam::new(ty));
}
}
for ty in abi_type_to_cl(&sig.returns, ptr_ty) {
cl_sig.returns.push(AbiParam::new(ty));
}
cl_sig
}Cada variante de Inst tem exatamente um destino no Cranelift. Exemplos:
use cranelift_codegen::ir::{types, InstBuilder, condcodes::{IntCC, FloatCC}, MemFlags};
pub fn lower_inst(inst: &Inst, ctx: &mut FnCtx) {
match inst {
// Aritmética inteira
Inst::IAdd { dst, lhs, rhs } => {
let v = ctx.builder.ins().iadd(ctx.val(*lhs), ctx.val(*rhs));
ctx.bind(*dst, v);
}
Inst::IAddImm { dst, lhs, imm } => {
let v = ctx.builder.ins().iadd_imm(ctx.val(*lhs), *imm);
ctx.bind(*dst, v);
}
Inst::IShlImm { dst, lhs, imm } => {
let v = ctx.builder.ins().ishl_imm(ctx.val(*lhs), *imm);
ctx.bind(*dst, v);
}
Inst::SShrImm { dst, lhs, imm } => {
let v = ctx.builder.ins().sshr_imm(ctx.val(*lhs), *imm);
ctx.bind(*dst, v);
}
Inst::BAndImm { dst, lhs, imm } => {
let v = ctx.builder.ins().band_imm(ctx.val(*lhs), *imm);
ctx.bind(*dst, v);
}
// Conversões de tipo
Inst::IReduce { dst, src, to } => {
let cl_ty = ctx.hir_to_cl(to);
let v = ctx.builder.ins().ireduce(cl_ty, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::UExtend { dst, src, to } => {
let cl_ty = ctx.hir_to_cl(to);
let v = ctx.builder.ins().uextend(cl_ty, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::SExtend { dst, src, to } => {
let cl_ty = ctx.hir_to_cl(to);
let v = ctx.builder.ins().sextend(cl_ty, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::FPromote { dst, src } => {
let v = ctx.builder.ins().fpromote(types::F64, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::FDemote { dst, src } => {
let v = ctx.builder.ins().fdemote(types::F32, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::CvtFromSint { dst, src, to } => {
let cl_ty = ctx.hir_to_cl(to);
let v = ctx.builder.ins().fcvt_from_sint(cl_ty, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::CvtToSintSat { dst, src, to } => {
let cl_ty = ctx.hir_to_cl(to);
let v = ctx.builder.ins().fcvt_to_sint_sat(cl_ty, ctx.val(*src));
ctx.bind(*dst, v);
}
Inst::Bitcast { dst, src, to } => {
let cl_ty = ctx.hir_to_cl(to);
let v = ctx.builder.ins().bitcast(cl_ty, MemFlags::new(), ctx.val(*src));
ctx.bind(*dst, v);
}
// Comparações — icmp retorna I8; uextend para I64 se necessário para ABI
Inst::ICmp { dst, cond, lhs, rhs } => {
let cc = int_cond_to_cl(cond);
let cmp = ctx.builder.ins().icmp(cc, ctx.val(*lhs), ctx.val(*rhs));
// extend para I64 para uso como valor ABI (bool retornado como i64)
let v = ctx.builder.ins().uextend(types::I64, cmp);
ctx.bind(*dst, v);
}
Inst::FCmp { dst, cond, lhs, rhs } => {
let cc = float_cond_to_cl(cond);
let cmp = ctx.builder.ins().fcmp(cc, ctx.val(*lhs), ctx.val(*rhs));
let v = ctx.builder.ins().uextend(types::I64, cmp);
ctx.bind(*dst, v);
}
// Narrow loads/stores
Inst::ULoad8 { dst, ptr, offset } => {
let v = ctx.builder.ins().uload8(MemFlags::trusted(), ctx.val(*ptr), *offset);
ctx.bind(*dst, v);
}
Inst::SLoad16 { dst, ptr, offset } => {
let v = ctx.builder.ins().sload16(MemFlags::trusted(), ctx.val(*ptr), *offset);
ctx.bind(*dst, v);
}
Inst::IStore8 { val, ptr, offset } => {
ctx.builder.ins().istore8(MemFlags::trusted(), ctx.val(*val), ctx.val(*ptr), *offset);
}
Inst::IStore32 { val, ptr, offset } => {
ctx.builder.ins().istore32(MemFlags::trusted(), ctx.val(*val), ctx.val(*ptr), *offset);
}
// Branchless select
Inst::Select { dst, cond, on_true, on_false } => {
let v = ctx.builder.ins().select(
ctx.val(*cond), ctx.val(*on_true), ctx.val(*on_false)
);
ctx.bind(*dst, v);
}
// Atomics
Inst::AtomicRmw { dst, op, ptr, val, order } => {
let cl_op = rmw_op_to_cl(op);
let cl_ord = mem_order_to_cl(order);
let v = ctx.builder.ins().atomic_rmw(cl_op, cl_ord, ctx.val(*ptr), ctx.val(*val));
ctx.bind(*dst, v);
}
Inst::AtomicCas { dst, ptr, expected, replacement, order } => {
let cl_ord = mem_order_to_cl(order);
let v = ctx.builder.ins().atomic_cas(
cl_ord, ctx.val(*ptr), ctx.val(*expected), ctx.val(*replacement)
);
ctx.bind(*dst, v);
}
Inst::Fence { order } => {
ctx.builder.ins().fence(mem_order_to_cl(order));
}
// GC stack map
Inst::DeclareGcValue { val } => {
ctx.builder.declare_value_needs_stack_map(ctx.val(*val));
}
// ... demais variantes seguem o mesmo padrão
}
}pub fn lower_term(term: &Terminator, ctx: &mut FnCtx) {
match term {
Terminator::Return(vals) => {
let cl_vals: Vec<_> = vals.iter().map(|v| ctx.val(*v)).collect();
ctx.builder.ins().return_(&cl_vals);
}
Terminator::Jump { target, args } => {
let cl_block = ctx.block(*target);
let cl_args: Vec<_> = args.iter().map(|v| ctx.val(*v)).collect();
ctx.builder.ins().jump(cl_block, &cl_args);
}
Terminator::Brif { cond, then_block, then_args, else_block, else_args } => {
let cl_then = ctx.block(*then_block);
let cl_else = ctx.block(*else_block);
let cl_then_args: Vec<_> = then_args.iter().map(|v| ctx.val(*v)).collect();
let cl_else_args: Vec<_> = else_args.iter().map(|v| ctx.val(*v)).collect();
ctx.builder.ins().brif(
ctx.val(*cond),
cl_then, &cl_then_args,
cl_else, &cl_else_args,
);
}
Terminator::Switch { index, default, cases } => {
// Usa Switch builder do cranelift_frontend — backend escolhe br_table vs busca binária
let mut sw = cranelift_frontend::Switch::new();
for &(key, blk) in cases {
sw.set_entry(key, ctx.block(blk));
}
sw.emit(&mut ctx.builder, ctx.val(*index), ctx.block(*default));
}
Terminator::TailCall { sym, args } => {
let fref = ctx.get_func_ref(sym);
let cl_args: Vec<_> = args.iter().map(|v| ctx.val(*v)).collect();
ctx.builder.ins().return_call(fref, &cl_args);
}
Terminator::TailCallIndirect { fn_ptr, args } => {
let sig = ctx.indirect_sig.clone();
let cl_args: Vec<_> = args.iter().map(|v| ctx.val(*v)).collect();
ctx.builder.ins().return_call_indirect(sig, ctx.val(*fn_ptr), &cl_args);
}
Terminator::Trap { code } => {
ctx.builder.ins().trap(trap_hint_to_cl(code));
}
}
}pub fn lower_func(mir: &MirFunc, ctx: &mut ModuleCtx) {
let sig = build_signature(mir, ctx);
let func_id = ctx.module.declare_function(&mir.name, Linkage::Local, &sig).unwrap();
let mut cl_ctx = ctx.module.make_context();
cl_ctx.func.signature = sig;
let mut fb_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut cl_ctx.func, &mut fb_ctx);
// 1. Criar todos os blocos Cranelift antecipadamente
let cl_blocks: Vec<Block> = mir.blocks.iter()
.map(|_| builder.create_block())
.collect();
// 2. Adicionar parâmetros ao bloco de entrada (= params da função)
builder.append_block_params_for_function_params(cl_blocks[0]);
// 3. Adicionar block params para os demais blocos (phis SSA)
for (i, bb) in mir.blocks.iter().enumerate().skip(1) {
for (_, hir_ty) in &bb.params {
let cl_ty = hir_ty_to_cl(hir_ty, ctx.ptr_ty);
builder.append_block_param(cl_blocks[i], cl_ty);
}
}
// 4. Lower cada bloco
let mut fn_ctx = FnCtx::new(builder, cl_blocks, ctx);
for (i, bb) in mir.blocks.iter().enumerate() {
fn_ctx.builder.switch_to_block(fn_ctx.cl_blocks[i]);
// Seal imediatamente se não há back-edges (blocos de loop selados após o back-edge)
if !is_loop_header(mir, bb.id) {
fn_ctx.builder.seal_block(fn_ctx.cl_blocks[i]);
}
for inst in &bb.insts {
lower_inst(inst, &mut fn_ctx);
}
lower_term(&bb.term, &mut fn_ctx);
}
// Selar headers de loop após back-edges terem sido emitidos
for bb in &mir.blocks {
if is_loop_header(mir, bb.id) {
fn_ctx.builder.seal_block(fn_ctx.cl_blocks[bb.id as usize]);
}
}
fn_ctx.builder.finalize();
ctx.module.define_function(func_id, &mut cl_ctx).unwrap();
// 5. Extrair stack maps para GC
if let Some(compiled) = cl_ctx.compiled_code() {
register_stack_maps(func_id, compiled, ctx);
}
ctx.module.clear_context(&mut cl_ctx);
}Todos os namespaces — compilado independente do compilador.
Mover de: src/namespaces/
rts-runtime/src/
lib.rs
rt_all.rs — register_all_symbols(builder: &mut JITBuilder)
namespaces/ — estrutura atual mantida
rt_all.rs exporta:
/// Registra todos os símbolos extern "C" no JITBuilder.
/// Chamado uma vez no startup do módulo JIT.
pub fn register_all_symbols(builder: &mut cranelift_jit::JITBuilder) {
use crate::namespaces::*;
builder.symbol("__RTS_FN_NS_IO_PRINT", io::print as *const u8);
builder.symbol("__RTS_FN_NS_GC_STRING_NEW", gc::string_new as *const u8);
// ... todos os ~400+ símbolos
}rts-runtime não importa cranelift-codegen. A dep de cranelift_jit::JITBuilder
é aceitável aqui — é apenas para registro de ponteiros, não para emissão de IR.
Source TS
│
▼ rts-parser
SWC parse → rts-ast
Passes AST: expand_async, purity_pass, reduce_pass, array_methods_pass
│
▼ rts-hir
lower(ast) → HIR com HirType em cada nó
refine_type_from_init → narrow types propagados (I8..I128, F32, F64)
numeric_promotion → regras de promoção explícitas
│
▼ rts-mir
lower(hir) → MIR SSA (BasicBlock + block params + Inst + Terminator)
passes:
fold.rs → strength reduction (mul→shl, div→shr, mod→and) + constant folding
dce.rs → eliminação de código morto
narrow.rs → canonicalização de i8/i16/i32 ops com masks corretas
│
▼ rts-codegen
abi_bridge.rs → AbiType/LoweredSignature → cranelift ir::Type / ir::Signature
lower/inst.rs → Inst → builder.ins().* (1:1 mecânico)
lower/term.rs → Terminator → jump/brif/return_call/Switch
lower/func.rs → MirFunc → FunctionBuilder completo
lower/gc.rs → declare_value_needs_stack_map + stack map registry
JITModule (rts run) ou ObjectModule (rts compile)
│
▼
Binário nativo / memória executável
HirType |
cranelift ir::Type |
Instrução aritmética | Loads/Stores |
|---|---|---|---|
I8 |
types::I8 |
iadd, isub, imul + mask |
uload8/istore8 |
I16 |
types::I16 |
iadd, isub, imul + mask |
uload16/istore16 |
I32 |
types::I32 |
iadd, isub, imul, sdiv |
sload32/istore32 |
I64 |
types::I64 |
iadd, isub, imul, sdiv |
load/store |
I128 |
types::I128 |
iadd, isub, imul |
load/store (16 bytes) |
U8 |
types::I8 |
iadd, udiv, urem + band_imm 0xFF |
uload8/istore8 |
U16 |
types::I16 |
iadd, udiv, urem + mask |
uload16/istore16 |
U32 |
types::I32 |
iadd, udiv, urem |
uload32/istore32 |
U64 |
types::I64 |
iadd, udiv, urem |
load/store |
F32 |
types::F32 |
fadd, fsub, fmul, fdiv |
load(F32)/store |
F64 |
types::F64 |
fadd, fsub, fmul, fdiv |
load(F64)/store |
Number |
types::F64 |
fadd, fsub, fmul, fdiv |
load(F64)/store |
Bool |
types::I64 |
icmp → uextend I64 |
— |
Handle |
types::I64 |
— (opaco) | load(I64)/store |
Str |
[I64, I64] |
— (2 slots: ptr+len) | — |
I8 → I64: sextend(I64, val) ou uextend(I64, val) conforme semântica
I64 → I8: ireduce(I8, val) ou band_imm(val, 0xFF) + ireduce
I32 → I64: sextend(I64, val)
F64 → I64: fcvt_to_sint_sat(I64, val) (saturante — não trapa)
I64 → F64: fcvt_from_sint(F64, val)
F32 → F64: fpromote(F64, val)
F64 → F32: fdemote(F32, val)
i64 bits → f64: bitcast(F64, MemFlags::new(), val) (transmute)
- Criar
Cargo.tomlde workspace na raiz - Criar estrutura de diretórios
crates/ - Mover
rts-diagnostics(zero deps, movimento puro) - Mover
rts-ast— adicionarTsPrimitivecom narrow types - Build verde com monólito ainda em
src/+ novos crates vazios - CI rodando workspace (
cargo test --workspace)
-
rts-abi— desacoplarLoweredSignaturedecranelift-codegen; implementarAbiTypepróprio -
rts-parser— moversrc/parser/+ passes AST -
rts-linker— moversrc/linker/+runtime_objects.rs -
rts-runtime— moversrc/namespaces/+ exportarregister_all_symbols -
rts-codegen— moversrc/codegen/+ criarabi_bridge.rs;setup.rscomuse_egraphs=true -
rts-cli— mover restante
Marco: cargo test --workspace verde, mesmos benchmarks.
- Definir
HirTypecompleto (I8–I128, F32, F64, todos os tipos) emrts-hir/ir.rs(commit c2eb724) - Implementar
lower.rs: AST → HIR comHirTypeem cada nó (commit c2eb724) - Implementar
type_refine.rs+numeric_promotion(commit c2eb724) - Implementar
type_map.rscomCraneliftTypeHint(commit c2eb724) - Adaptar
rts-codegenpara consumir HIR — primeira fatia: dep +lower_funcnocompile_programdescartando resultado (commit 2320e1f) - Testes unitários do crate
rts-hir(commit c1a22b2, 21/21) - Refinar
HirFuncret type a partir do body (return stmts com literais inteiros → I64), não só da anotação TS textual - Codegen consumir hints HIR:
Callde user fn em local sem anotação usahir_return_types[&fn]em vez de cair emNumber - Validar: hot loops com
let i = step(i)emitemiadddireto semfcvt
Marco intermediário (atual): crate rts-hir completo, conectado ao pipeline, suite verde.
Marco final: suite mantida + rts ir mostra ausência de fcvt em sites tipo i = userFn(i) quando a fn retorna inteiro.
Marco entregue (etapa 3.19): routing híbrido com opt-in por nome
no compile_program. RTS_USE_MIR=fn1,fn2,... faz cada fn listada
tentar caminho HIR→MIR→Cranelift; se a fn tem trap, signature
mismatch com declaração AST, ou placeholders silenciosos, cai no AST
automaticamente. Modo "all" (RTS_USE_MIR=all) ativa para todas as
fns mas alguns programs travam até features faltantes serem
modeladas (member em this/objetos, classes, async/await com Promise).
Permite migração incremental sem quebrar produção (suite default
sem env var continua 622/632 verde).
- Crate
rts-mircriado com IR completo:MirFunc,BasicBlock,Inst(60+ variantes incluindo aritmética inteira/float, bitwise, shifts, conversões, comparações, loads/stores narrow, atomics, GC),Terminator(Return/Jump/Brif/Switch/TailCall/Trap),IntCond/FloatCondespelhando Cranelift. 7 testes verde. -
lower.rs: HIR → MIR — cobertura ampliada (literais, aritmética, comparações, bitwise, shifts, casts, if/else, while, do-while, for clássico com init/cond/update, break/continue com loop stack, ternary→Select, let/const, return; constructs ainda unsupported caem emTerminator::Trap) -
passes/fold.rs: constant folding (IAdd/ISub/IMul/SDiv/SRem/BAnd/BOr/BXor de IConst→IConst) + strength reduction (mul→shl, urem→band, sdiv→sshr, ops com const→*Imm) -
passes/dce.rs: eliminação de código morto com fixed-point (preserva side-effecting: Store, CallExtern, AtomicStore/Rmw/Cas, Fence, DeclareGcValue) -
passes/narrow.rs: canonicalização I8/U8 (mask 0xFF) e I16/U16 (mask 0xFFFF) após IAdd/ISub/IMul/INeg/IShl -
passes/verify.rs: invariantes (block ids match position, ValueIds em range, BlockIds em range, params count consistente) -
crates/rts-codegen/src/mir_codegen/: lower MIR → Cranelift IR isolado (não plugado no pipeline). hint_bridge.rs converte CraneliftTypeHint → cl::Type; lower.rs traduz Inst/Terminator 1:1 via FunctionBuilder; mod.rs expõeextern_resolver_default()que resolve namespaces RTS viacrate::abi::SPECS. 32 testes cobrindo: (a) 10 unitários JIT-execute que constroem MirFunc na mão (incluindo Switch e loop com phi nodes); (b) 7 de integração viarts_parser::parse_source+ lower HIR + lower MIR + passes + verify; (c) 5 smoke single-fn TS→native; (d) 3 smoke multi-fn comInst::CallUsercross-fn (recursão, chains); (e) 4 testes de extern call (math.sqrt(4.0)==2.0JIT executando__RTS_FN_NS_MATH_SQRT); (f) 3 testes de string literal:Inst::StrLitaloca data segment, expande StrPtr param em (ptr,len),io.stdout_write("ABC")JIT escreve no stdout real e retorna 3. -
verify.rs: invariantes em debug (tipo de cada ValueId consistente, blocos terminados) - Adaptar
rts-codegenpara consumir MIR viamir_codegen/(commit bdf25c0) - Testes unitários para cada pass (sem Cranelift — testam só o MIR; rts-mir 51/51)
- Routing híbrido AST ↔ MIR com opt-in (commit 7e8451a)
- MIR ativado por default (commit f7b924b, etapa 3.25)
- Cobertura expandida para 438 fns reais (commit 23dd4b7, etapa 3.26)
Marco entregue: cargo test --workspace verde, suite TS 622/632
(mesmas 10 falhas pré-existentes), MIR ON por default sem regressão.
Ver subseção "Fase 4 entregas (parcial)" no topo deste arquivo para detalhes de commits e métricas.
- [~] Suporte de primeira classe a
i8/i16/i32em TS — parser/HIR aceitam, narrow pass canonicaliza ops, runtime overflow não testado em produção - Operações bitwise e narrow no codegen:
uload8,istore16,sload32(storage real em código de produção) - Atomics expostos via namespace
atomic+ tipos corretos no MIR (etapa 4.1) - Inlining de fns pequenas no MIR pass (etapas 4.2/4.3/4.7) + CSE (4.5) + FMA (4.8)
- Escape analysis: handles que não escapem → stack slot via
StackAlloc - SIMD via
HirType::V128(VecKind)+Inst::Splat,ExtractLane,InsertLane
| Otimização | Cranelift faz? | Onde fazer no RTS |
|---|---|---|
| Constant folding interprocessual | Não (só intraproc com e-graphs) | rts-mir/passes/fold.rs |
| Inlining | Não | rts-mir/passes/inline.rs |
| Escape analysis | Não | rts-mir/passes/escape.rs |
| Strength reduction (mul→shl) | Parcial (e-graphs) | rts-mir/passes/fold.rs — garante antes do CL |
| Narrow type canonicalização | Não | rts-mir/passes/narrow.rs |
| Autovectorização | Não | SIMD deve ser explícito no HIR/MIR |
| DCE interprocessual | Não | rts-mir/passes/dce.rs |
| Loop invariant code motion | Limitado | MIR Fase 4 |
O que o Cranelift faz bem e não precisamos reimplementar:
- Register allocation (linear scan)
- Instruction selection (escolhe formas nativas por alvo)
- Constant folding intraprocessual (e-graphs com
use_egraphs=true) - CSE intraprocessual
- Peephole:
x + 0 → x,x * 1 → x,x & 0 → 0,x | ~0 → ~0 - Seleção de
br_tablevs binary search (viaSwitchbuilder) - Tail call via
return_call(quandopreserve_frame_pointers=true)
| Perry | RTS (pós-refator) | Razão |
|---|---|---|
| NaN-boxing (f64 uniforme) | ABI tipada com AbiType |
RTS é otimizado para TS bem-tipado; tipos nativos em registradores |
| LLVM backend | Cranelift (mantém) | build leve, licenciamento simples, embutido |
| 30+ crates | ~10 crates | overhead de manutenção menor |
| HIR com monomorfização | HIR sem monomorph (por ora) | generics TS não são tão agressivos |
| MIR implícito no LLVM | MIR explícito leve | Cranelift não faz constant folding/DCE/inlining |
perry-jsruntime (fallback) |
sem fallback | RTS compila tudo AOT/JIT |
| Narrow types via NaN-box | Narrow types via ireduce/uextend/sextend explícitos |
Mantém ABI tipada, semântica correta |
- Não migrar para NaN-boxing. Refator gigante; ganho só em código
any-pesado; ABI tipada é vantagem competitiva do RTS. - Não trocar Cranelift por LLVM. Cranelift cobre tudo via
use_egraphs=true; o gap (autovec, cross-IPO) é coberto pelo MIR layer. - Não reimplementar o que o Cranelift já faz (
x*1,x+0, peephole básico) —use_egraphs=truejá cobre isso intraprocessualmente. - Não criar HIR com monomorfização agressiva. Generics TS simples não justificam.
- Não separar cada namespace em crate. Perry fez isso para swap de backend por plataforma — RTS não tem esse requisito ainda.
- Não usar
I128em hot paths. A maioria dos backends decompõe em 2 × I64 — usarumulhi/smulhi+imulquando precisar de produto 128-bit.
| Otimização habilitada | Benchmark afetado | Ganho estimado |
|---|---|---|
refine_type_from_init (HIR) + iadd_imm |
hot loops com counters não-anotados | 5–15% |
| Strength reduction MIR (mul→shl, mod→and) | qualquer aritmética com constantes | 2–8% |
| Constant folding MIR interprocessual | pipelines com constantes propagadas | variável |
| DCE MIR | código gerado menor → JIT mais rápido | tempo de compile |
Narrow types corretos (uload8 etc.) |
código que trabalha com bytes/buffers | correto + ~5% |
| Inlining MIR (Fase 4) | fns pequenas em hot loop | 10–30% potencial |
Monte Carlo já está em 16.9 ms AOT — ganho esperado pequeno ali. Maior ganho: código TS natural sem anotações + código de baixo nível (buffers, FFI).
docs/specs/cranelift-explications.md— guia completo da API Cranelift 0.131PERRY_ANALYSIS.md— análise comparativa PerryTS vs RTSdocs/specs/silent-parallelism.md— passes AST a migrar pararts-parserdocs/specs/async-promise-function.md— subsistema async (permanece emrts-runtime)- Issues: #90 (block params), #96 (otimizações pendentes), #97 (function pointers)
- Cranelift
use_egraphsdesign: https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/docs/egraphs.md