Control Flow Graph - Exemples de base

Construction et analyse de Control Flow Graphs (CFG) depuis le code Catnip.

Importation

from catnip import Catnip
import catnip._rs as rs

c = Catnip()

CFG simple

▸ Assignment linéaire

ir = c.parse('x = 1; y = 2; z = 3')
cfg = rs.cfg.build_cfg_from_ir(ir, 'linear')

print(cfg)
# CFG: linear
# Entry: Some(Some("entry"))
# Exit: Some(Some("exit"))
# Blocks: 2
#
# entry:
#   0: Op { ident: 13, args: ..., ... }
#   1: Op { ident: 13, args: ..., ... }
#   2: Op { ident: 13, args: ..., ... }
#   -> exit (fallthrough)
#
# exit:

print(f'Blocks: {cfg.num_blocks}')  # 2
print(f'Edges: {cfg.num_edges}')    # 1

CFG conditionnel

▸ If/else (structure en diamant)

ir = c.parse('''
if x > 0 {
    y = 1
} else {
    y = 2
}
''')
cfg = rs.cfg.build_cfg_from_ir(ir, 'if_else')

print(cfg.num_blocks)  # 5 : entry, if_then, if_else, if_merge, exit
print(cfg.num_edges)   # 5 : entry→then, entry→else, then→merge, else→merge, merge→exit

Le CFG crée une structure en diamant :

       entry
      /     \
   then     else
      \     /
       merge
         |
        exit

CFG avec boucles

▸ While loop

ir = c.parse('''
while x < 10 {
    x = x + 1
}
''')
cfg = rs.cfg.build_cfg_from_ir(ir, 'while')

print(cfg.num_blocks)  # 5 : entry, while_header, while_body, while_exit, exit
print(cfg.num_edges)   # 5 : dont un back edge

# Structure :
#   entry → header ⇄ body
#              ↓
#            exit

▸ For loop

ir = c.parse('''
for x in range(10) {
    y = y + x
}
''')
cfg = rs.cfg.build_cfg_from_ir(ir, 'for')

# Structure similaire au while
print(cfg.num_blocks)  # 5

CFG avec contrôle de flux

▸ Break dans une boucle

ir = c.parse('''
while x < 10 {
    if x == 5 {
        break
    }
    x = x + 1
}
''')
cfg = rs.cfg.build_cfg_from_ir(ir, 'break_loop')

print(cfg.num_blocks)  # 8
print(cfg.num_edges)   # 9

# Le break crée un edge direct vers while_exit

▸ Continue dans une boucle

ir = c.parse('''
while x < 10 {
    if x % 2 == 0 {
        continue
    }
    process(x)
    x = x + 1
}
''')
cfg = rs.cfg.build_cfg_from_ir(ir, 'continue_loop')

# Continue crée un edge direct vers while_header

Blocs atteignables

ir = c.parse('x = 1; y = 2')
cfg = rs.cfg.build_cfg_from_ir(ir, 'test')

reachable = cfg.get_reachable_blocks()
print(f'Blocs atteignables: {reachable}')

unreachable = cfg.get_unreachable_blocks()
print(f'Blocs non atteignables: {unreachable}')

# Nettoyage
if unreachable:
    cfg.remove_unreachable_blocks()

Visualisation

▸ Génération DOT

ir = c.parse('''
if x > 0 {
    y = 1
} else {
    y = 2
}
''')
cfg = rs.cfg.build_cfg_from_ir(ir, 'example')

# Obtenir le DOT
dot = cfg.to_dot()
print(dot[:100])  # Affiche le début

▸ Export vers fichier

# Exporter et afficher les commandes
cfg.visualize('output.dot')
# CFG exported to output.dot
# Visualize with: dot -Tpng output.dot -o output.png

# Puis avec graphviz :
# dot -Tpng output.dot -o output.png

Le format DOT inclut :

  • Styling : entry (vert), exit (rouge)
  • Couleurs : edges true (vert), false (rouge)
  • Style dashed pour break/continue
  • Affichage des instructions de chaque bloc

Représentation

ir = c.parse('x = 1')
cfg = rs.cfg.build_cfg_from_ir(ir, 'test')

# repr : format court
print(repr(cfg))
# <CFG test blocks=2 edges=1>

# str : format détaillé avec structure
print(str(cfg))
# CFG: test
# Entry: Some(Some("entry"))
# ...

Les blocs basiques sont comme des wagons de train : ils vont toujours dans le même sens, sauf quand ils bifurquent. À ce moment-là ce ne sont plus des wagons, ce sont des aiguillages. Mais on continue de les appeler wagons par cohérence administrative.