graph TD
A[Python Function Input] --> B[AST Parser]
B --> C[Custom Compiler]
C --> D[VM Bytecode]
D --> E[Candidate Synthesis]
E --> F[LUT Generation]
F --> G[Selection Strategy]
G --> H[Code Replacement]
H --> I[Optimized Bytecode]
I --> J[Verification]
J --> K[Python Code Generation]
The EiLUT system follows a multi-stage optimization pipeline:
- AST Compiler: Converts Python functions to custom VM bytecode
- Candidate Synthesizer: Identifies pure computational windows
- LUT Generator: Creates lookup tables for identified windows
- Selection Engine: Chooses optimal LUT replacements
- Code Generator: Produces optimized Python with embedded LUTs
The system uses a stack-based VM with the following instruction categories:
graph LR
subgraph "Stack Operations"
A1[PUSH]
A2[POP]
A3[DUP]
A4[SWAP]
end
subgraph "Arithmetic"
B1[ADD]
B2[SUB]
B3[MUL]
end
subgraph "Bitwise"
C1[AND]
C2[OR]
C3[XOR]
C4[NOT]
C5[SHL/SHR]
end
subgraph "Control Flow"
D1[JZ]
D2[JMP]
D3[CMPZ]
end
subgraph "Memory"
E1[LOAD]
E2[LOCAL_LOAD]
E3[LOCAL_STORE]
E4[LUT]
end
Each opcode is defined with:
- Name: Operation identifier
- Stack Pops: Number of values consumed from stack
- Stack Pushes: Number of values produced to stack
- Code Size: Instruction size in bytes
- Time Cost: Execution time in cycles
sequenceDiagram
participant S as Stack
participant VM as VM Engine
participant M as Memory
VM->>S: PUSH value
VM->>S: LOAD arg_index
VM->>S: ADD (pop 2, push 1)
VM->>M: LOCAL_STORE index
VM->>S: RET (pop 1)
The compiler uses the visitor pattern to traverse Python AST nodes:
graph TD
A[Python Function] --> B[ast.parse]
B --> C[AST Tree]
C --> D[Compiler.visit]
D --> E{Node Type}
E -->|BinOp| F[emit arithmetic ops]
E -->|Name| G[emit LOAD/LOCAL_LOAD]
E -->|Constant| H[emit PUSH]
E -->|If| I[emit conditional jumps]
E -->|While| J[emit loop structure]
F --> K[Bytecode List]
G --> K
H --> K
I --> K
J --> K
For the function:
def f(a, b):
return a + bThe compilation process generates:
LOAD 0(load parametera)LOAD 1(load parameterb)ADD(add top two stack values)RET(return top of stack)
The system identifies "pure" computational windows that can be replaced with LUTs:
graph TD
A[Scan Bytecode] --> B{Check Window i to j}
B --> C[Stack Balance Analysis]
C --> D{Valid Balance?}
D -->|No| E[Try Next Window]
D -->|Yes| F[Simulate All Input Combinations]
F --> G{All Simulations Pure?}
G -->|No| E
G -->|Yes| H[Generate LUT Table]
H --> I[Calculate Cost/Benefit]
I --> J[Add to Candidates]
E --> K[Next Window]
J --> K
K --> L{More Windows?}
L -->|Yes| B
L -->|No| M[Return Candidates]
For a window from instruction
The minimum depth during execution must be negative (indicating required inputs):
For each valid window, the system:
- Determines required inputs:
$\text{inputs} = -\text{min_depth}$ - Generates all possible input combinations:
${0, 1, ..., 2^{BITWIDTH}-1}^{inputs}$ - Simulates window execution for each combination
- Records output mappings:
$\text{LUT}: \mathbb{Z}^{inputs} \rightarrow \mathbb{Z}^{outputs}$
The cost model compares original instruction sequence with LUT replacement:
Where:
-
$\text{LUT_data_size} = \frac{\text{entries} \times \text{outputs} \times \text{BITWIDTH} + 7}{8}$ bytes $\text{entries} = 2^{\text{BITWIDTH} \times \text{inputs}}$
graph TD
A[Start with All Candidates] --> B{Strategy Type}
B -->|Size| C[Sort by size_delta ASC]
B -->|Time| D[Sort by time_delta ASC]
C --> E[Filter: size_delta < 0]
D --> F[Filter: time_delta < 0]
E --> G[Greedy Selection Loop]
F --> G
G --> H{Next Candidate}
H --> I{Overlaps Selected?}
I -->|Yes| J[Skip Candidate]
I -->|No| K[Add to Selection]
K --> L[Mark Instructions as Covered]
L --> M{More Candidates?}
J --> M
M -->|Yes| H
M -->|No| N[Return Selection]
The system detects equivalent LUT tables using hash-based deduplication:
Equivalent tables share the same LUT ID, reducing memory usage.
Selected candidates are replaced in the original bytecode:
sequenceDiagram
participant O as Original Code
participant R as Replacer
participant N as New Code
loop For each instruction
O->>R: Current instruction
R->>R: Check if start of replacement
alt Is replacement start
R->>N: Emit LUT instruction
R->>R: Skip to end of window
else Normal instruction
R->>N: Copy original instruction
end
end
The final step regenerates executable Python code with embedded LUT dictionaries:
# Generated LUT tables
lut_1 = {(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}
def f(a, b):
return lut_1[(a, b)] # Replaces original a + bgraph LR
A[Generate Test Inputs] --> B[Run Original Bytecode]
A --> C[Run Optimized Bytecode]
B --> D[Original Result]
C --> E[Optimized Result]
D --> F{Results Match?}
E --> F
F -->|Yes| G[PASSED]
F -->|No| H[FAILED]
The simulator executes both original and optimized bytecode with identical inputs to verify correctness.
For
- 1 param:
$[1]$ - 2 params:
$[1, 2]$ - 3 params:
$[1, 2, 3]$ - 4 params:
$[1, 2, 3, 4]$
The system includes a Flask-based web IDE:
graph TD
A[Browser] --> B[Flask Server]
B --> C[Load Examples]
B --> D[Process Optimization Request]
D --> E[Run Optimization Pipeline]
E --> F[Return Results]
F --> G[Display in Browser]
C --> H[Populate UI]
The debug mode runs batch optimizations on all examples:
graph TD
A[Load examples.json] --> B[For each example]
B --> C[Run optimization]
C --> D[Log results]
D --> E[Parse status/metrics]
E --> F[Add to summary table]
F --> G{More examples?}
G -->|Yes| B
G -->|No| H[Generate Pandas DataFrame]
H --> I[Print Summary Table]
- Bit Width: Limited to 4-bit integers (configurable via
BITWIDTH) - Window Size: Maximum 6 instructions (
MAX_WIN = 6) - LUT Inputs: Maximum 3 inputs per LUT (due to exponential table growth)
- Pure Operations: Only side-effect-free operations can be replaced
-
Candidate Generation:
$O(n \times w \times 2^{b \times i})$ where:-
$n$ = number of instructions -
$w$ = maximum window size -
$b$ = bit width -
$i$ = maximum inputs per LUT
-
-
Selection:
$O(c^2)$ where$c$ = number of candidates (due to overlap checking)
EiLUT provides a comprehensive framework for optimizing small integer computations in MicroPython through systematic LUT replacement. The multi-stage pipeline ensures correctness while achieving significant size and time optimizations for suitable code patterns.
The system's modular design allows for easy extension of the instruction set, cost models, and selection strategies, making it adaptable to different target platforms and optimization goals.