Workspace IndexAlgorithms › Day 21

Dataflow Analysis TODO

Algorithms · Day 21 / 100 · B. Compilers, Runtimes & VMs (Day 20-35)

Concept

Dataflow analysis is a static-analysis technique that sets up equations for facts that hold at each point on a program's control-flow graph, then iterates to a fixed point over a lattice. Constant propagation attaches each variable a lattice value of "not yet known / constant c / not a constant," propagates it forward, and at merge points takes the meet of the two values — when different constants meet, the result drops to "not a constant." Dead-code elimination works in the opposite direction, using backward liveness analysis: if a definition of a variable is never used afterward and has no side effects, that definition is removed. The reason this approach is guaranteed to terminate is that the lattice has finite height and the transfer functions are monotone. Once code is in SSA form, each variable is defined exactly once, so the use-def relation is explicit, making both analyses considerably simpler and faster to implement.

Understanding why an optimizer removes some code and keeps other code — especially why optimization stops in front of an operation with side effects — is necessary to read generated bytecode or machine code and explain performance or gas differences.

Code & Formula

# 데이터플로 분석 — 상수 전파(전방향 격자 고정점)와 죽은 코드 제거(후방향 liveness)를 작은 IR에 적용한다.

# IR: (dest, op, args) 튜플의 리스트. op 는 "const" 또는 이항 연산자 이름.
program = [
    ("a", "const", 3),
    ("b", "const", 4),
    ("c", "+", ("a", "b")),      # c = a + b = 7 (상수로 전파됨)
    ("d", "const", 10),          # 이후 어디서도 쓰이지 않음 -> 죽은 코드
    ("e", "*", ("c", "b")),      # e = c * b
    ("out", "+", ("e", 0)),      # 반환값
]

def constant_propagate(program):
    consts = {}
    folded = []
    for dest, op, args in program:
        if op == "const":
            consts[dest] = args
            folded.append((dest, "const", args))
            continue
        a, b = args
        va = consts.get(a) if isinstance(a, str) else a
        vb = consts.get(b) if isinstance(b, str) else b
        if va is not None and vb is not None:
            val = va + vb if op == "+" else va * vb
            consts[dest] = val
            folded.append((dest, "const", val))
        else:
            folded.append((dest, op, args))           # NAC: 상수 아님, 그대로 둠
    return folded, consts

def dead_code_eliminate(program, root="out"):
    used = {root}
    changed = True
    while changed:                                     # 고정점까지 반복 (후방향 liveness)
        changed = False
        for dest, op, args in program:
            if dest in used and op != "const":
                for a in args:
                    if isinstance(a, str) and a not in used:
                        used.add(a)
                        changed = True
    return [instr for instr in program if instr[0] in used]

folded, consts = constant_propagate(program)
live = dead_code_eliminate(folded)

print("상수 전파 결과:", consts)
print("죽은 코드 제거 전:", [i[0] for i in folded])
print("죽은 코드 제거 후:", [i[0] for i in live], "(d 제거됨)")

Exercise

Build a small IR by hand with about three basic blocks and a branch, tabulate the constant-propagation lattice values at each iteration until you reach a fixed point, and then remove the dead instructions.

Practical Connection

Compiling a Solidity contract with optimization turned on and off and comparing the assembly lets you see directly which SSTOREs and operations constant propagation and dead-code elimination actually removed to cut gas.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

데이터플로 분석 TODO

Algorithms · Day 21 / 100 · B. 컴파일러·런타임·VM (Day 20–35)

상수 전파·죽은 코드 제거

개념

데이터플로 분석은 프로그램의 제어흐름 그래프 위에서 각 지점에 성립하는 사실(fact)을 방정식으로 세우고, 격자(lattice) 위에서 고정점에 도달할 때까지 반복 계산하는 정적 분석 기법이다. 상수 전파는 각 변수에 '아직 모름 / 상수 c / 상수 아님'이라는 격자 값을 붙여 전방향으로 전파하고, 분기 합류점에서는 두 값의 meet를 취해 서로 다른 상수가 만나면 '상수 아님'으로 떨어뜨린다. 죽은 코드 제거는 반대로 후방향 liveness 분석을 써서, 어떤 변수의 정의가 이후 어디서도 쓰이지 않고 부수효과도 없으면 그 정의를 삭제한다. 격자의 높이가 유한하고 전이 함수가 단조(monotone)이면 반복이 반드시 종료한다는 것이 이 방식의 정당성 근거다. SSA 형태로 변환해 두면 각 변수가 한 번만 정의되므로 use-def 관계가 명시적이 되어 두 분석 모두 훨씬 단순하고 빠르게 구현된다.

옵티마이저가 왜 어떤 코드는 지우고 어떤 코드는 남기는지, 특히 부수효과가 있는 연산 앞에서 최적화가 멈추는 이유를 이해해야 생성된 바이트코드나 기계어를 읽고 성능·가스 차이를 설명할 수 있다.

코드 · 수식

# 데이터플로 분석 — 상수 전파(전방향 격자 고정점)와 죽은 코드 제거(후방향 liveness)를 작은 IR에 적용한다.

# IR: (dest, op, args) 튜플의 리스트. op 는 "const" 또는 이항 연산자 이름.
program = [
    ("a", "const", 3),
    ("b", "const", 4),
    ("c", "+", ("a", "b")),      # c = a + b = 7 (상수로 전파됨)
    ("d", "const", 10),          # 이후 어디서도 쓰이지 않음 -> 죽은 코드
    ("e", "*", ("c", "b")),      # e = c * b
    ("out", "+", ("e", 0)),      # 반환값
]

def constant_propagate(program):
    consts = {}
    folded = []
    for dest, op, args in program:
        if op == "const":
            consts[dest] = args
            folded.append((dest, "const", args))
            continue
        a, b = args
        va = consts.get(a) if isinstance(a, str) else a
        vb = consts.get(b) if isinstance(b, str) else b
        if va is not None and vb is not None:
            val = va + vb if op == "+" else va * vb
            consts[dest] = val
            folded.append((dest, "const", val))
        else:
            folded.append((dest, op, args))           # NAC: 상수 아님, 그대로 둠
    return folded, consts

def dead_code_eliminate(program, root="out"):
    used = {root}
    changed = True
    while changed:                                     # 고정점까지 반복 (후방향 liveness)
        changed = False
        for dest, op, args in program:
            if dest in used and op != "const":
                for a in args:
                    if isinstance(a, str) and a not in used:
                        used.add(a)
                        changed = True
    return [instr for instr in program if instr[0] in used]

folded, consts = constant_propagate(program)
live = dead_code_eliminate(folded)

print("상수 전파 결과:", consts)
print("죽은 코드 제거 전:", [i[0] for i in folded])
print("죽은 코드 제거 후:", [i[0] for i in live], "(d 제거됨)")

연습

세 개 정도의 기본 블록과 분기가 있는 작은 IR을 손으로 만들고, 상수 전파 격자 값을 반복마다 표로 적어 고정점까지 굴린 뒤 죽은 명령을 지워 보라.

실무 · Verex 연결

Solidity 컨트랙트를 최적화 옵션을 켜고 끄며 컴파일해 어셈블리를 비교하면 상수 전파와 죽은 코드 제거가 실제로 어떤 SSTORE·연산을 없애 가스를 줄였는지 눈으로 확인할 수 있다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 20. IR과 SSA 형식22. 레지스터 할당(그래프 컬러링)과 스필 비용 →