Ioannis Savidis

96 papers A* 2A 3C 29Misc 1Journal 26Unranked 33
YearRankTypeTitle / Venue / Authors
2026 J jnl
ACM Trans. Design Autom. Electr. Syst.
Vaibhav Venugopal Rao, Kyle Juretus, Ioannis Savidis
2025 conf
ACM Great Lakes Symposium on VLSI
Alec Aversa, Hasin Ishraq Reefat, Naghmeh Karimi, Ioannis Savidis
2025 J jnl
CoRR
Pratik Shrestha, Saran Phatharodom, Alec Aversa, David Blankenship, Zhengfeng Wu, Ioannis Savidis
2025 C conf
ISCAS
Zhengfeng Wu, Pratik Shrestha, Saran Phatharodom, Ioannis Savidis
2025 J jnl
CoRR
Zhengfeng Wu, Ziyi Chen, Nnaemeka Achebe, Vaibhav Venugopal Rao, Pratik Shrestha, Ioannis Savidis
2025 C conf
ICCD
Pratik Shrestha, Ioannis Savidis
2025 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Ziyi Chen, Ioannis Savidis
2025 A conf
DATE
Hasin Ishraq Reefat, Alec Aversa, Ioannis Savidis, Naghmeh Karimi
2024 C conf
ISCAS
Ziyi Chen, Ioannis Savidis
2024 conf
ISQED
Zhengfeng Wu, Ioannis Savidis
2024 C conf
ISCAS
Vaibhav Venugopal Rao, Kyle Juretus, Ioannis Savidis
2024 conf
ISQED
Pratik Shrestha, Ioannis Savidis
2024 conf
ACM Great Lakes Symposium on VLSI
Pratik Shrestha, Alec Aversa, Saran Phatharodom, Ioannis Savidis
2024 C conf
ISCAS
Zhengfeng Wu, Ioannis Savidis
2024 conf
ACM Great Lakes Symposium on VLSI
Alec Aversa, Ioannis Savidis
2023 C conf
ISCAS
Ziyi Chen, Ioannis Savidis
2023 C conf
ISCAS
Zhengfeng Wu, Ioannis Savidis
2023 C conf
ISCAS
Pratik Shrestha, Ioannis Savidis
2023 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Vaibhav Venugopal Rao, Kyle Juretus, Ioannis Savidis
2023 conf
MLCAD
Zhengfeng Wu, Isabel Song, Ioannis Savidis
2022 conf
ISQED
Ali Mirzaeian, Zhi Tian, Sai Manoj P. D., Banafsheh S. Latibari, Ioannis Savidis, Houman Homayoun, Avesta Sasan
2022 J jnl
CoRR
Ali Mirzaeian, Zhi Tian, Sai Manoj P. D., Banafsheh S. Latibari, Ioannis Savidis, Houman Homayoun, Avesta Sasan
2022 conf
ISQED
Vaibhav Venugopal Rao, Avesta Sasan, Ioannis Savidis
2022 ed.
ACM Great Lakes Symposium on VLSI
Ioannis Savidis, Avesta Sasan, Himanshu Thapliyal, Ronald F. DeMara
2022 conf
MLCAD
Pratik Shrestha, Saran Phatharodom, Ioannis Savidis
2022 conf
HOST
Vaibhav Venugopal Rao, Kyle Juretus, Ioannis Savidis
2022 conf
ACM Great Lakes Symposium on VLSI
Tanmoy Chowdhury, Ashkan Vakil, Banafsheh Saber Latibari, Sayed Aresh Beheshti-Shirazi, Ali Mirzaeian, Xiaojie Guo, Sai Manoj P. D., Houman Homayoun, Ioannis Savidis, Liang Zhao, Avesta Sasan
2022 C conf
ISCAS
Ziyi Chen, Ioannis Savidis
2022 C conf
ISCAS
Pratik Shrestha, Ioannis Savidis
2022 C conf
ISCAS
Zhengfeng Wu, Ioannis Savidis
2022 conf
MLCAD
Zhengfeng Wu, Ioannis Savidis
2021 conf
ACM Great Lakes Symposium on VLSI
Sayed Aresh Beheshti-Shirazi, Ashkan Vakil, Sai Manoj P. D., Ioannis Savidis, Houman Homayoun, Avesta Sasan
2021 conf
AICAS
Md Shazzad Hossain, Ioannis Savidis
2021 ed.
ACM Great Lakes Symposium on VLSI
Yiran Chen, Victor V. Zhirnov, Avesta Sasan, Ioannis Savidis
2021 J jnl
IEEE Trans. Comput. Aided Des. Integr. Circuits Syst.
Kyle Juretus, Ioannis Savidis
2021 J jnl
IEEE J. Emerg. Sel. Topics Circuits Syst.
Md Shazzad Hossain, Ioannis Savidis
2021 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Vaibhav Venugopal Rao, Ioannis Savidis
2021 C conf
ICCD
Ziyi Chen, Ioannis Savidis
2021 conf
ACM Great Lakes Symposium on VLSI
Saran Phatharodom, Avesta Sasan, Ioannis Savidis
2021 J jnl
IEEE Trans. Comput. Aided Des. Integr. Circuits Syst.
Kyle Juretus, Ioannis Savidis
2021 conf
MLCAD
Zhengfeng Wu, Ioannis Savidis
2020 conf
MLCAD
Zhengfeng Wu, Ioannis Savidis
2020 J jnl
IEEE Trans. Comput. Aided Des. Integr. Circuits Syst.
Kyle Juretus, Ioannis Savidis
2020 J jnl
Microelectron. J.
Md Shazzad Hossain, Ioannis Savidis
2020 A conf
ISLPED
Md Shazzad Hossain, Ioannis Savidis
2020 C conf
ISCAS
Saran Phatharodom, Nagarajan Kandasamy, Ioannis Savidis
2020 J jnl
J. Hardw. Syst. Secur.
Marko Jacovic, Kyle Juretus, Nagarajan Kandasamy, Ioannis Savidis, Kapil R. Dandekar
2020 J jnl
Microelectron. J.
Md Shazzad Hossain, Ioannis Savidis
2020 C conf
ISCAS
Kyle Juretus, Ioannis Savidis
2020 C conf
ISCAS
Vaibhav Venugopal Rao, Kyle Juretus, Ioannis Savidis
2019 C conf
ICCD
Divya Pathak, Ioannis Savidis
2019 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Massimo Alioto, Magdy S. Abadir, Tughrul Arslan, Chirn Chye Boon, Andreas Burg, Chip-Hong Chang, Meng-Fan Chang, Yao-Wen Chang, Poki Chen, Pasquale Corsonello, Paolo Crovetti, Shiro Dosho, Rolf Drechsler, Ibrahim Abe M. Elfadel, Ruonan Han, Masanori Hashimoto, Chun-Huat Heng, Deukhyoun Heo, Tsung-Yi Ho, Houman Homayoun, Yuh-Shyan Hwang, Ajay Joshi, Rajiv V. Joshi, Tanay Karnik, Chulwoo Kim, Tony Tae-Hyoung Kim, Jaydeep P. Kulkarni, Volkan Kursun, Yoonmyung Lee, Hai Helen Li, Huawei Li, Prabhat Mishra, Baker Mohammad, Mehran Mozaffari Kermani, Makoto Nagata, Koji Nii, Partha Pratim Pande, Bipul C. Paul, Vasilis F. Pavlidis, José Pineda de Gyvez, Ioannis Savidis, Patrick Schaumont, Fabio Sebastiano, Anirban Sengupta, Mingoo Seok, Mircea R. Stan, Mark Tehranipoor, Aida Todri-Sanial, Marian Verhelst, Valerio Vignoli, Xiaoqing Wen, Jiang Xu, Wei Zhang, Zhengya Zhang, Jun Zhou, Mark Zwolinski, Stacey Weber
2019 C conf
ISCAS
Kyle Juretus, Ioannis Savidis
2019 C conf
ISCAS
Vaibhav Venugopal Rao, Ioannis Savidis
2019 C conf
ISCAS
Md Shazzad Hossain, Ioannis Savidis
2019 A conf
ISLPED
Ragh Kuttappa, Baris Taskin, Scott Lerner, Vasil Pano, Ioannis Savidis
2019 conf
ACM Great Lakes Symposium on VLSI
Kyle Juretus, Vaibhav Venugopal Rao, Ioannis Savidis
2019 J jnl
J. Hardw. Syst. Secur.
James Chacko, Kyle Juretus, Marko Jacovic, Cem Sahin, Nagarajan Kandasamy, Ioannis Savidis, Kapil R. Dandekar
2018 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Mohammad Khavari Tavana, Mohammad Hossein Hajkazemi, Divya Pathak, Ioannis Savidis, Houman Homayoun
2018 conf
APCCAS
Kyle Juretus, Ioannis Savidis
2018 C conf
ICCD
David Werner, Kyle Juretus, Ioannis Savidis, Mark Hempstead
2018 conf
IGSC
Md Shazzad Hossain, Ioannis Savidis
2018 C conf
ISCAS
Md Shazzad Hossain, Ioannis Savidis
2018 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Divya Pathak, Ioannis Savidis
2018 conf
ASP-DAC
Hossein Sayadi, Divya Pathak, Ioannis Savidis, Houman Homayoun
2018 conf
ACM Great Lakes Symposium on VLSI
Ioannis Savidis, Swarup Bhunia, Gang Qu, Matthew J. Casto, Jeremy Muldavin
2018 C conf
ISCAS
Kyle Juretus, Ioannis Savidis
2018 conf
APCCAS
Vaibhav Venugopal Rao, Ioannis Savidis
2017 conf
MWSCAS
Md Shazzad Hossain, Ioannis Savidis
2017 conf
SLIP
Isuru Daulagala, Ioannis Savidis
2017 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Krishnendu Chakrabarty, Massimo Alioto, Bevan M. Baas, Chirn Chye Boon, Meng-Fan Chang, Naehyuck Chang, Yao-Wen Chang, Chip-Hong Chang, Shih-Chieh Chang, Poki Chen, Masud H. Chowdhury, Pasquale Corsonello, Ibrahim Abe M. Elfadel, Said Hamdioui, Masanori Hashimoto, Tsung-Yi Ho, Houman Homayoun, Yuh-Shyan Hwang, Rajiv V. Joshi, Tanay Karnik, Mehran Mozaffari Kermani, Chulwoo Kim, Tae-Hyoung Kim, Jaydeep P. Kulkarni, Eren Kursun, Erik Larsson, Hai (Helen) Li, Huawei Li, Patrick P. Mercier, Prabhat Mishra, Makoto Nagata, Arun S. Natarajan, Koji Nii, Partha Pratim Pande, Ioannis Savidis, Mingoo Seok, Sheldon X.-D. Tan, Mark Tehranipoor, Aida Todri-Sanial, Miroslav N. Velev, Xiaoqing Wen, Jiang Xu, Wei Zhang, Zhengya Zhang, Stacey Weber Jackson
2017 conf
HOST
Vaibhav Venugopal Rao, Ioannis Savidis
2017 Misc conf
ICNC
James Chacko, Kyle Juretus, Marko Jacovic, Cem Sahin, Nagarajan Kandasamy, Ioannis Savidis, Kapil R. Dandekar
2017 conf
LATS
Vaibhav Venugopal Rao, Ioannis Savidis
2017 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Divya Pathak, Houman Homayoun, Ioannis Savidis
2017 conf
ACM Great Lakes Symposium on VLSI
Divya Pathak, Houman Homayoun, Ioannis Savidis
2016 conf
SAMOS
Kostas Siozios, Ioannis Savidis, Dimitrios Soudris
2016 C conf
ISCAS
Divya Pathak, Mohammad Hossein Hajkazemi, Mohammad Khavari Tavana, Houman Homayoun, Ioannis Savidis
2016 J jnl
Microelectron. J.
Ioannis Savidis, Berkehan Ciftcioglu, Jie Xu, Jianyun Hu, Manish Jain, Rebecca Berman, Jing Xue, Peng Liu, Duncan Moore, Gary Wicks, Michael C. Huang, Hui Wu, Eby G. Friedman
2016 conf
ACM Great Lakes Symposium on VLSI
Divya Pathak, Mohammad Hossein Hajkazemi, Mohammad Khavari Tavana, Houman Homayoun, Ioannis Savidis
2016 conf
ACM Great Lakes Symposium on VLSI
Kyle Juretus, Ioannis Savidis
2016 C conf
ISCAS
Kyle Juretus, Ioannis Savidis
2016 C conf
ISCAS
Md Shazzad Hossain, Ioannis Savidis
2015 A* conf
DAC
Mohammad Khavari Tavana, Mohammad Hossein Hajkazemi, Divya Pathak, Ioannis Savidis, Houman Homayoun
2015 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Ioannis Savidis, Boris Vaisband, Eby G. Friedman
2015 C conf
ICCD
Mohammad Khavari Tavana, Divya Pathak, Mohammad Hossein Hajkazemi, Maria Malik, Ioannis Savidis, Houman Homayoun
2014 conf
SoCC
Divya Pathak, Ioannis Savidis
2014 C conf
ISCAS
Boris Vaisband, Ioannis Savidis, Eby G. Friedman
2013 J jnl
IEEE J. Solid State Circuits
Ioannis Savidis, Selçuk Köse, Eby G. Friedman
2011 J jnl
IEEE Trans. Very Large Scale Integr. Syst.
Vasilis F. Pavlidis, Ioannis Savidis, Eby G. Friedman
2011 C conf
ISCAS
Ioannis Savidis, Vasilis F. Pavlidis, Eby G. Friedman
2011 J jnl
Microelectron. J.
Jinhui Wang, Ioannis Savidis, Eby G. Friedman
2010 A* conf
ISCA
Jing Xue, Alok Garg, Berkehan Ciftcioglu, Jianyun Hu, Shang Wang, Ioannis Savidis, Manish Jain, Rebecca Berman, Peng Liu, Michael C. Huang, Hui Wu, Eby G. Friedman, Gary Wicks, Duncan Moore
2010 J jnl
Microelectron. J.
Ioannis Savidis, Syed M. Alam, Ankur Jain, Scott Pozder, Robert E. Jones, Ritwik Chatterjee
2008 conf
CICC
Vasilis F. Pavlidis, Ioannis Savidis, Eby G. Friedman
2008 C conf
ISCAS
Ioannis Savidis, Eby G. Friedman
redb/extractors/decompiler/bninja/analysis/cfg_features.py
← Index redb/extractors/decompiler/bninja/analysis/cfg_features.py python
import struct
from collections import deque
from typing import Optional

import blake3
import mmh3


# ---------------------------------------------------------------------------
# Task 1.1: Core Graph Utilities
# ---------------------------------------------------------------------------

def bfs_order(successors: list[list[int]], n: int) -> list[int]:
    """
    BFS traversal from node 0 (entry block), returns node indices in visit order.
    Unreachable nodes appended at the end.
    """
    if n == 0:
        return []

    visited = set()
    order = []
    queue = deque([0])
    visited.add(0)

    while queue:
        idx = queue.popleft()
        order.append(idx)
        for target in successors[idx]:
            if target not in visited:
                visited.add(target)
                queue.append(target)

    # Append unreachable blocks (dead code)
    for i in range(n):
        if i not in visited:
            order.append(i)

    return order


def bfs_max_depth(successors: list[list[int]], n: int) -> int:
    """
    Maximum BFS depth from entry block (node 0).
    Replaces the per-block depth column with a single scalar.
    """
    if n == 0:
        return 0

    depth = {0: 0}
    max_d = 0
    queue = deque([0])

    while queue:
        node = queue.popleft()
        for s in successors[node]:
            if s not in depth:
                depth[s] = depth[node] + 1
                if depth[s] > max_d:
                    max_d = depth[s]
                queue.append(s)

    return max_d


# ---------------------------------------------------------------------------
# Task 1.2: Back-Edge Detection (Iterative DFS)
# ---------------------------------------------------------------------------

def count_back_edges(successors: list[list[int]], n: int) -> int:
    """
    Count natural loops via iterative DFS back-edge detection.
    A back edge is an edge to a GRAY (in-stack) node.

    Iterative to avoid stack overflow on functions with 1000+ blocks
    (common in obfuscated malware, VM dispatchers, unrolled loops).
    """
    if n == 0:
        return 0

    WHITE, GRAY, BLACK = 0, 1, 2
    color = [WHITE] * n
    back_edges = 0

    stack = [(0, iter(successors[0]))]
    color[0] = GRAY

    while stack:
        u, children = stack[-1]
        try:
            v = next(children)
            if color[v] == GRAY:
                back_edges += 1
            elif color[v] == WHITE:
                color[v] = GRAY
                stack.append((v, iter(successors[v])))
        except StopIteration:
            color[u] = BLACK
            stack.pop()

    return back_edges


# ---------------------------------------------------------------------------
# Task 1.3: Topology Hash
# ---------------------------------------------------------------------------

def compute_topology_hash(
    successors: list[list[int]],
    bfs: list[int],
    n: int,
) -> bytes:
    """
    BLAKE3 hash of BFS-ordered canonical adjacency.
    Pure graph shape — ignores all block content.
    Two functions with identical control flow structure produce identical hashes.

    Returns 16 bytes (128-bit).
    """
    if n == 0:
        return b'\x00' * 16

    # Remap: original index -> BFS position
    remap = {original: position for position, original in enumerate(bfs)}

    canonical = bytearray()
    for position in range(n):
        original_idx = bfs[position]
        remapped_succs = sorted(
            remap[s] for s in successors[original_idx] if s in remap
        )
        # Pack: node_index (2 bytes) + num_successors (1 byte) + successor indices (2 bytes each)
        canonical.extend(struct.pack('<HB', position, len(remapped_succs)))
        for s in remapped_succs:
            canonical.extend(struct.pack('<H', s))

    return blake3.blake3(bytes(canonical)).digest(length=16)


# ---------------------------------------------------------------------------
# Task 1.4: MD-Index (Top-Down and Bottom-Up)
# ---------------------------------------------------------------------------

def compute_md_index_topdown(
    successors: list[list[int]],
    predecessors: list[list[int]],
    bfs: list[int],
) -> int:
    """
    BinDiff-style top-down MD-index.
    Hash of (in_degree, out_degree) sequence in BFS order from entry.
    Returns UInt64.
    """
    if not bfs:
        return 0

    degree_bytes = bytearray()
    for idx in bfs:
        in_deg = min(len(predecessors[idx]), 255)
        out_deg = min(len(successors[idx]), 255)
        degree_bytes.extend(struct.pack('<BB', in_deg, out_deg))

    h = blake3.blake3(bytes(degree_bytes)).digest(length=8)
    return struct.unpack('<Q', h)[0]


def compute_md_index_bottomup(
    successors: list[list[int]],
    predecessors: list[list[int]],
    n: int,
) -> int:
    """
    Bottom-up MD-index: BFS from exit blocks (no successors),
    traversing edges in reverse.
    Returns UInt64.
    """
    if n == 0:
        return 0

    exits = [i for i in range(n) if len(successors[i]) == 0]
    if not exits:
        exits = [n - 1]  # Fallback: use last block

    visited = set(exits)
    order = []
    queue = deque(exits)

    while queue:
        idx = queue.popleft()
        order.append(idx)
        for pred in predecessors[idx]:
            if pred not in visited:
                visited.add(pred)
                queue.append(pred)

    # Append unreachable blocks
    for i in range(n):
        if i not in visited:
            order.append(i)

    degree_bytes = bytearray()
    for idx in order:
        in_deg = min(len(predecessors[idx]), 255)
        out_deg = min(len(successors[idx]), 255)
        degree_bytes.extend(struct.pack('<BB', in_deg, out_deg))

    h = blake3.blake3(bytes(degree_bytes)).digest(length=8)
    return struct.unpack('<Q', h)[0]


# ---------------------------------------------------------------------------
# Task 1.5: Prime Product
# ---------------------------------------------------------------------------

# Small primes assigned to LLIL opcode categories.
# Keys are the integer values of binaryninja.LowLevelILOperation enum members.
# We use integer keys so this module doesn't import binaryninja.
#
# Mapping rationale: same operation class -> same prime.
# Using LLIL (not native asm) makes this architecture-independent.
#
# Populated at import time by cfg.py using the real LowLevelILOperation enum values.
# Unknown ops map to prime 1 (identity element) in compute_prime_product().
LLIL_OP_PRIMES: dict[int, int] = {}


def compute_prime_product(llil_operations: list[int]) -> int:
    """
    Product of small primes assigned to each LLIL opcode.
    Position-independent: block reordering doesn't change the result.
    Mod 2^64 for fixed-size storage.

    Args:
        llil_operations: flat list of LLIL operation enum integer values
                         for all instructions in the function.
    Returns:
        UInt64 prime product, or 0 if no instructions.
    """
    if not llil_operations:
        return 0

    product = 1
    for op in llil_operations:
        prime = LLIL_OP_PRIMES.get(op, 1)
        product = (product * prime) % (2**64)

    return product


# ---------------------------------------------------------------------------
# Task 1.6: ACFG Block Features
# ---------------------------------------------------------------------------

# Instruction category indices for ACFG feature vectors
CAT_ARITHMETIC = 0
CAT_LOGIC = 1
CAT_TRANSFER = 2
CAT_CALL = 3
CAT_COMPARISON = 4
CAT_MEMORY = 5
CAT_OTHER = 6

# Maps LLIL operation integer values to category indices.
# Populated at import time by cfg.py using the real LowLevelILOperation enum.
LLIL_OP_CATEGORIES: dict[int, int] = {}


def build_block_features(
    block_llil_ops: list[list[int]],
    successors: list[list[int]],
    n: int,
) -> list[list[int]]:
    """
    Extract Gemini-style ACFG features per block.

    Args:
        block_llil_ops: per-block list of LLIL operation integer values.
                        block_llil_ops[i] is the list of ops for block i.
                        Empty list if LLIL unavailable for that block.
        successors: index-based adjacency list.
        n: number of blocks.

    Returns:
        List of [instr_count, arithmetic, logic, transfer, call, comparison,
                 memory, successor_count] per block. All values capped at 65535.
    """
    features = []
    for i in range(n):
        cats = [0, 0, 0, 0, 0, 0, 0]
        ops = block_llil_ops[i] if i < len(block_llil_ops) else []
        for op in ops:
            cat = LLIL_OP_CATEGORIES.get(op, CAT_OTHER)
            cats[cat] += 1

        instr_count = len(ops)
        features.append([
            min(instr_count, 65535),
            min(cats[CAT_ARITHMETIC], 65535),
            min(cats[CAT_LOGIC], 65535),
            min(cats[CAT_TRANSFER], 65535),
            min(cats[CAT_CALL], 65535),
            min(cats[CAT_COMPARISON], 65535),
            min(cats[CAT_MEMORY], 65535),
            min(len(successors[i]), 65535),
        ])

    return features


# ---------------------------------------------------------------------------
# Task 1.7: CFG Feature TLSH
# ---------------------------------------------------------------------------

def compute_cfg_feature_tlsh(
    bb_features: list[list[int]],
    bfs: list[int],
) -> Optional[str]:
    """
    TLSH hash of BFS-ordered per-block feature vectors.
    Captures both structure (BFS ordering) and instruction distribution.

    Returns TLSH hex string or None if too few bytes for TLSH (< 50).
    """
    import tlsh as _tlsh

    feature_bytes = bytearray()
    for idx in bfs:
        feats = bb_features[idx]
        feature_bytes.extend(struct.pack(
            '<HBBBBBBB',
            min(feats[0], 65535),
            min(feats[1], 255),
            min(feats[2], 255),
            min(feats[3], 255),
            min(feats[4], 255),
            min(feats[5], 255),
            min(feats[6], 255),
            min(feats[7], 255),
        ))

    if len(feature_bytes) < 50:
        return None

    try:
        h = _tlsh.hash(bytes(feature_bytes))
        return h if h and h != 'TNULL' else None
    except Exception:
        return None


# ---------------------------------------------------------------------------
# Task 1.8: WL-MinHash
# ---------------------------------------------------------------------------

# Pre-computed seeds for MinHash permutations.
NUM_WL_MINHASH_PERMS = 128
_WL_MINHASH_SEEDS = list(range(NUM_WL_MINHASH_PERMS))  # Seeds 0..127


def compute_wl_minhash(
    successors: list[list[int]],
    predecessors: list[list[int]],
    bb_features: list[list[int]],
    n: int,
    iterations: int = 3,
) -> list[int]:
    """
    Weisfeiler-Leman MinHash for fuzzy topology similarity.

    Initial labels: mmh3 hash of per-block ACFG feature tuple (content-aware).
    WL refinement: incorporate sorted neighbor labels at each iteration.
    MinHash: 128-permutation signature over shingle set.

    Returns list of 128 uint8 values, or [255]*128 sentinel for empty functions.
    """
    if n == 0:
        return [255] * NUM_WL_MINHASH_PERMS

    # Initial labels: hash of instruction category tuple per block
    labels = []
    for i in range(n):
        feats = bb_features[i] if i < len(bb_features) else [0] * 8
        # mmh3 with seed=0 for initial labels
        label = mmh3.hash(str(tuple(feats)), 0) & 0xFFFFFFFF
        labels.append(label)

    # Collect shingles: (iteration, label) pairs as strings for mmh3
    shingles: set[str] = set()

    # Iteration 0: individual block labels
    for label in labels:
        shingles.add(f"0:{label}")

    # WL iterations: refine labels by neighborhood aggregation
    for iteration in range(1, iterations + 1):
        new_labels = []
        for i in range(n):
            succ_labels = tuple(sorted(labels[s] for s in successors[i]))
            pred_labels = tuple(sorted(labels[p] for p in predecessors[i]))
            composite = f"{labels[i]}|{succ_labels}|{pred_labels}"
            new_label = mmh3.hash(composite, 0) & 0xFFFFFFFF
            new_labels.append(new_label)
            shingles.add(f"{iteration}:{new_label}")
        labels = new_labels

    if not shingles:
        return [255] * NUM_WL_MINHASH_PERMS

    # Compute MinHash signature using mmh3 with different seeds
    shingle_list = list(shingles)
    signature = []
    for seed in _WL_MINHASH_SEEDS:
        min_val = 0xFFFFFFFF
        for s in shingle_list:
            h = mmh3.hash(s, seed) & 0xFFFFFFFF
            if h < min_val:
                min_val = h
        # Compress to uint8 for storage
        signature.append(min_val & 0xFF)

    return signature


# ---------------------------------------------------------------------------
# Task 1.9: Packed Adjacency
# ---------------------------------------------------------------------------

def pack_adjacency(successors: list[list[int]]) -> list[int]:
    """
    Pack CFG edges as Array(UInt32).
    Each UInt32 = (source_index << 16) | target_index.
    Supports up to 65,535 blocks per function.
    """
    edges = []
    for src, targets in enumerate(successors):
        for tgt in targets:
            if src < 65536 and tgt < 65536:
                edges.append((src << 16) | tgt)
    return edges