[Review] The Execution Stack on One Page TODO
Concept
You can view the execution stack as a chain of stages that turns source code into actual hardware behavior. At the front is the frontend, which lexes and parses into an AST and does semantic analysis to pin down types and names; in the middle is the optimization stage, which works on an intermediate representation like SSA and performs constant propagation, inlining, dead code elimination, and so on. At the back, instruction selection, register allocation, and code generation produce target instructions (native machine code or VM bytecode), which an interpreter or JIT then executes while the runtime manages memory, GC, and exceptions. Mapped onto Ethereum: Solidity goes through an IR like Yul to become EVM bytecode, and the client's EVM interpreter executes that, deducting gas for every opcode. The point of this map is "which decision gets locked in at which stage" — when you're looking at an optimization failure or a performance problem, pinpointing the responsible stage first is the key move.
When you hit a performance issue or unexpected behavior, if you can't immediately narrow down whether to look at the source, compiler optimizations, VM execution, or the runtime, debugging drifts into guesswork. The layer map is the baseline for that narrowing.
Code & Formula
# [복습] 실행 계층 지도 — 렉싱 -> 파싱(AST) -> 최적화(상수 전파) -> 실행 까지, 한 표현식으로 전 단계를 통과시킨다.
import re
# 1) 프론트엔드: 렉서 — 소스 문자열을 토큰으로 쪼갠다.
def lex(src):
return re.findall(r"\d+|[+\-*/()]", src)
# 1) 프론트엔드: 파서 — 토큰을 AST(중첩 튜플)로 만든다. 우선순위: * / > + -
def parse(tokens):
pos = 0
def peek():
return tokens[pos] if pos < len(tokens) else None
def parse_expr():
nonlocal pos
node = parse_term()
while peek() in ('+', '-'):
op = tokens[pos]; pos += 1
node = (op, node, parse_term())
return node
def parse_term():
nonlocal pos
node = parse_factor()
while peek() in ('*', '/'):
op = tokens[pos]; pos += 1
node = (op, node, parse_factor())
return node
def parse_factor():
nonlocal pos
tok = tokens[pos]; pos += 1
if tok == '(':
node = parse_expr(); pos += 1 # skip ')'
return node
return int(tok)
return parse_expr()
# 2) 미들엔드: 최적화 — 상수 전파/폴딩 (양쪽이 이미 리터럴이면 컴파일 타임에 계산해 버린다)
def constant_fold(node):
if isinstance(node, int):
return node
op, left, right = node
left, right = constant_fold(left), constant_fold(right)
if isinstance(left, int) and isinstance(right, int):
return {'+': left + right, '-': left - right, '*': left * right, '/': left // right}[op]
return (op, left, right)
# 3) 백엔드/런타임: 인터프리터 — 최종 AST(또는 폴딩된 상수)를 실제로 실행한다.
def interpret(node):
if isinstance(node, int):
return node
op, left, right = node
l, r = interpret(left), interpret(right)
return {'+': l + r, '-': l - r, '*': l * r, '/': l // r}[op]
source = "1 + 2 * (3 + 4)"
tokens = lex(source)
ast = parse(tokens)
folded = constant_fold(ast)
result = interpret(ast)
print("source :", source)
print("tokens :", tokens)
print("ast :", ast)
print("folded ast :", folded, " <- 상수 전파로 이미 스칼라 하나로 굳음")
print("result :", result)
docs/code/algorithms/algorithms-35.py
Exercise
On a single page, draw out the stages from source to hardware yourself, and next to each stage write its counterpart in the Ethereum stack (Solidity, Yul IR, EVM bytecode, the client's interpreter, gas metering) until there are no blanks left.
Practical Connection
When you hit a gas regression, figuring out whether it's from a change in compiler optimization settings, an opcode price change, or a change in contract logic is exactly the work of reading this map.
Where it lands in Jayverse
- CI: add a gas-regression bisector as a build step. When gas usage changes on a Verex or Wallet contract diff, use this stage map to bucket it — compiler/optimizer settings, EVM opcode pricing, or contract logic — before treating it as a real regression.
- Verex: pin the Solidity optimizer settings in CI, next to the frozen lockfiles. A silent optimizer-run-count or via-IR change is indistinguishable from a logic change in a raw gas diff, so pin it explicitly.
- gitboard: track per-contract gas-per-function over time as a dashboard series. Catch a regression at the stage that caused it, not just as "gas went up."
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| pin down | 명확히 규정하다, 콕 집어 확정하다 · 애매한 것을 정확한 값·의미로 고정할 때. "semantic analysis to pin down types and names" |
| lock in (a decision) | (결정을) 확정하다, 고정시키다 · 특정 단계에서 더 이상 바뀌지 않게 정해질 때. "which decision gets locked in at which stage" |
| drift into | (원치 않게) ~으로 흘러가다, 빠지다 · 체계 없이 진행하다가 결국 추측에 의존하게 될 때. "debugging drifts into guesswork" |
| narrow down | 범위를 좁히다 · 여러 가능성 중 원인이 될 만한 것을 줄여갈 때. "narrow down whether to look at the source" |
| counterpart | 대응물, 상응하는 것 · 한 체계의 요소가 다른 체계에서 무엇에 해당하는지 가리킬 때. "its counterpart in the Ethereum stack" |
| baseline | 기준선, 비교의 출발점 · 판단이나 비교의 기본 토대가 되는 것. "the layer map is the baseline for that narrowing" |
| pinpoint | 정확히 짚어내다 · 문제의 책임 소재를 정확히 찾아낼 때. "pinpointing the responsible stage first is the key move" |
| SSA | 정적 단일 대입 형식(Static Single Assignment) · 컴파일러 최적화 단계에서 각 변수가 딱 한 번만 대입되도록 표현하는 중간표현 형식. "an intermediate representation like SSA" |
| Yul | Solidity가 EVM 바이트코드가 되기 전에 거치는 중간언어(IR) · Solidity 컴파일 파이프라인에서 최적화가 실제로 이뤄지는 구체적 단계. "Solidity goes through an IR like Yul" |
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/.