MIR — o grafo de fluxo
Bloco básico, aresta rotulada, laço com aresta de volta e tratador de erro: a representação que responde por onde o programa passa.
A árvore diz o que o programa é; o grafo diz por onde ele passa. São perguntas diferentes, e as que mais interessam a um analisador só a segunda responde: este código é alcançável? este nome está definido em todo caminho que chega aqui? este local escapa do quadro?
construir devolve um corpo por ação — mais um chamado (programa) para o nível de topo, e um por rota do Kiln. Cada corpo é uma lista de blocos básicos: uma sequência de instruções sem desvio no meio, com arestas rotuladas para os seguintes.
classificar(n) 7 bloco(s), 2 inalcancavel(is)
bloco 0 [entrada] → 1 (sim), 2 (nao)
linha 5: ComparisonOp bigger
bloco 1 [sim] ⏹ yield
linha 6: YieldStatement
bloco 2 [nao] → 3 (sim), 4 (nao)
linha 7: ComparisonOp smaller
bloco 3 [sim] ⏹ yield
linha 8: YieldStatement
bloco 4 [nao] ⏹ yield
linha 10: YieldStatement
× bloco 5 [juncao] → 6
× bloco 6 [juncao] ⏹ fimO × marca o bloco inalcançável: todos os três ramos encerram com yield, então a junção não tem como ser atingida. E os dois blocos de junção, em vez de um, são o orif já aberto pelo HIR — o que mostra a normalização pagando o preço dela.
Os rótulos de aresta#
| Aresta | Quando |
|---|---|
| (sem rótulo) | cai no seguinte |
sim · nao | os dois lados de um given, de um laço ou de um guard |
volta | a aresta de trás de um laço — sem ela não é laço |
halt · skip | sai do laço, ou volta para a condição |
erro | do corpo de um monitor para o handle |
point · default | os ramos de um match |
defer | o corpo que roda na saída da ação |
adopt Arcane.Compilador as K
fonte := "n := 3\npersist n bigger 0:\n n -= 1\nout n\n"
blocos := K.blocos(fonte, "(programa)")
voltas := [b cycle b in blocos
given len([s cycle s in b["saidas"] given s["aresta"] is "volta"]) bigger 0]
assert len(voltas) is 1 // um laco tem uma aresta de voltaTrês decisões que valem lembrar#
A aresta de erro sai da ENTRADA do `monitor`, e não de cada instrução do corpo. Precisa ser assim para a análise ficar conservadora: o handle vê o estado de antes do corpo, que é o pior caso honesto. Uma aresta por instrução daria o mesmo resultado com um grafo três vezes maior.
O que roda fora da ordem é opaco. thread, parallel, server, crucible e as suas famílias entram como uma instrução. Abrir o corpo deles num grafo sequencial afirmaria uma ordem que não existe — e é exatamente sobre concorrência que uma afirmação errada custa.
adopt Arcane.Compilador as K
fonte := "monitor:\n x := 1\nhandle Error as e:\n out e.message\nensure:\n out 2\n"
rotulos := [b["rotulo"] cycle b in K.blocos(fonte, "(programa)")]
assert "tratador" in rotulos
assert "ensure" in rotulos