[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.
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/.