diff --git a/.github/workflows/deploy-doc.yml b/.github/workflows/deploy-doc.yml
index fcb356d..f604c1b 100644
--- a/.github/workflows/deploy-doc.yml
+++ b/.github/workflows/deploy-doc.yml
@@ -2,7 +2,7 @@ name: Build and Deploy doc
 
 on:
   push:
-    branches: [ main ]
+    branches: [main]
 
 env:
   CARGO_TERM_COLOR: always
@@ -24,30 +24,35 @@ jobs:
       url: ${{ steps.deployment.outputs.page_url }}
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-      with:
-        submodules: recursive
-    - name: Run sccache-cache
-      uses: mozilla-actions/sccache-action@v0.0.3
-    - name: Install wasm-pack
-      run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
-    - name: Build wasm packages
-      working-directory: ./crates/control-flow-graph-wasm
-      run: wasm-pack build --target web --release
-    - name: Move wasm packages
-      run: mv -f ./crates/control-flow-graph-wasm/pkg/* ./doc/static/tools/graph-editor/
-    - name: Build site 
-      run: docker run -u "$(id -u):$(id -g)" -v $PWD/doc:/app --workdir /app ghcr.io/getzola/zola:v0.16.0 build
-    - name: Build crate doc
-      run: cargo doc --workspace --document-private-items --all-features -r
-    - name: Move crate doc
-      run: sudo mv ./target/doc/* ./doc/public/
-    - name: Setup Pages
-      uses: actions/configure-pages@v2
-    - name: Upload artifact
-      uses: actions/upload-pages-artifact@v1
-      with:
-        path: './doc/public'
-    - name: Deploy to GitHub Pages
-      id: deployment
-      uses: actions/deploy-pages@v1
\ No newline at end of file
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Run sccache-cache
+        uses: mozilla-actions/sccache-action@v0.0.3
+      - name: Install wasm-pack
+        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
+      - name: Build wasm packages
+        working-directory: ./crates/control-flow-graph-wasm
+        run: wasm-pack build --target web --release
+      - name: Build wasm packages
+        working-directory: ./crates/ir-breadboard
+        run: wasm-pack build --target web --release
+      - name: Move wasm packages
+        run: mv -f ./crates/control-flow-graph-wasm/pkg/* ./doc/static/tools/graph-editor/
+      - name: Move wasm packages
+        run: mv -f ./crates/ir-breadboard/pkg/* ./doc/static/tools/ir-breadboard/
+      - name: Build site
+        run: docker run -u "$(id -u):$(id -g)" -v $PWD/doc:/app --workdir /app ghcr.io/getzola/zola:v0.16.0 build
+      - name: Build crate doc
+        run: cargo doc --workspace --document-private-items --all-features -r
+      - name: Move crate doc
+        run: sudo mv ./target/doc/* ./doc/public/
+      - name: Setup Pages
+        uses: actions/configure-pages@v2
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v1
+        with:
+          path: "./doc/public"
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v1
diff --git a/Cargo.toml b/Cargo.toml
index b50a161..9d7c11b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ indexmap = "2.1.0"
 itertools = "0.13.0"
 nom = "7.1.3"
 paste = "1.0.14"
-petgraph = "0.6.4"
+petgraph = { version = "0.6.4", features = ["serde-1"] }
 phf = { version = "0.11.2", features = ["macros"] }
 serde = { version = "1.0.195", features = ["derive"] }
 toml = "0.8.8"
@@ -54,3 +54,6 @@ required-features = ["build-binary"]
 [[bin]]
 name = "shuasm"
 required-features = ["build-binary"]
+
+[workspace]
+members = ["crates/control-flow-graph-wasm", "crates/ir-breadboard"]
diff --git a/crates/ir-breadboard/Cargo.toml b/crates/ir-breadboard/Cargo.toml
new file mode 100644
index 0000000..f7de815
--- /dev/null
+++ b/crates/ir-breadboard/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "ir-breadboard"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = { version = "1.0.203", features = ["derive"] }
+serde-wasm-bindgen = "0.6.5"
+wasm-bindgen = "0.2.92"
+come = { path = "../.." }
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[profile.release]
+opt-level = "z"
+lto = true
diff --git a/crates/ir-breadboard/src/lib.rs b/crates/ir-breadboard/src/lib.rs
new file mode 100644
index 0000000..1ebbe3f
--- /dev/null
+++ b/crates/ir-breadboard/src/lib.rs
@@ -0,0 +1,161 @@
+use std::str::FromStr;
+
+use come::ir::{
+    self,
+    analyzer::{self, control_flow::structural::FoldedCFG, ControlFlowGraph, IsAnalyzer},
+    optimize::{optimize as optimize_ir, pass::Pass},
+    IR,
+};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub fn parse(code: &str) -> JsValue {
+    let (_, parsed_ir) = ir::parse(code).unwrap();
+    let result = parsed_ir.as_function_definition();
+    serde_wasm_bindgen::to_value(&result).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn optimize(code: &str, pass: &str) -> String {
+    let ir_code = ir::parse(code).unwrap().1;
+    let pass = Pass::from_str(pass).unwrap();
+    let result = optimize_ir(vec![ir_code], vec![pass])
+        .into_iter()
+        .next()
+        .unwrap();
+    format!("{result}")
+}
+
+#[wasm_bindgen]
+#[derive(Clone, Debug)]
+pub struct Edge {
+    pub from: u32,
+    pub to: u32,
+    pub back: bool,
+}
+
+#[wasm_bindgen(getter_with_clone)]
+#[derive(Default, Debug)]
+pub struct CFGraph {
+    pub nodes: Vec<String>,
+    pub edges: Vec<Edge>,
+}
+
+#[wasm_bindgen]
+pub fn dump_control_flow_graph(code: &str) -> CFGraph {
+    let ir_code = ir::parse(code).unwrap().1;
+    if let IR::FunctionDefinition(f) = ir_code {
+        let cfg = analyzer::ControlFlowGraph::new();
+        let cfg = cfg.bind(&f);
+        let backedges = cfg.back_edges();
+        let mut result = CFGraph::default();
+        let g = cfg.graph();
+        for n in g.node_indices() {
+            if n.index() == g.node_count() - 1 {
+                result.nodes.push("_dummy_end".to_string());
+            } else {
+                result
+                    .nodes
+                    .push(cfg.basic_block_name_by_index(n.index()).to_string());
+            }
+        }
+        for e in g.edge_indices() {
+            let (from, to) = g.edge_endpoints(e).unwrap();
+            let is_backedge = backedges.contains(&(from.index() as _, to.index() as _));
+            result.edges.push(Edge {
+                from: from.index() as _,
+                to: to.index() as _,
+                back: is_backedge,
+            });
+        }
+        result
+    } else {
+        panic!("faq")
+    }
+}
+
+#[wasm_bindgen]
+pub fn structural_analysis(code: &str) -> JsValue {
+    let ir_code = ir::parse(code).unwrap().1;
+    let f = ir_code.as_function_definition();
+    let cfg = ControlFlowGraph::new();
+    let cfg = cfg.bind(f);
+    let folded = FoldedCFG::from_control_flow_graph(&cfg);
+    let result = folded.structural_analysis(&cfg);
+    serde_wasm_bindgen::to_value(&result).unwrap()
+}
+
+#[test]
+fn test_optimize() {
+    dbg!(optimize(
+        r"fn main() -> () {
+      %0 = add i32 1, 2
+      ret
+    }",
+        "FixIrreducible"
+    ));
+}
+
+#[test]
+fn test_dump_cfg() {
+    dbg!(dump_control_flow_graph(
+        r"fn test_condition(i32 %a, i32 %b) -> i32 {
+          test_condition_entry:
+            %a_0_addr = alloca i32
+            store i32 %a, address %a_0_addr
+            %b_0_addr = alloca i32
+            store i32 %b, address %b_0_addr
+            %result_0_addr = alloca i32
+            store i32 0, address %result_0_addr
+            %i_0_addr = alloca i32
+            %0 = load i32 %a_0_addr
+            store i32 %0, address %i_0_addr
+            j loop_0_condition
+          loop_0_condition:
+            %2 = load i32 %i_0_addr
+            %3 = load i32 %b_0_addr
+            %1 = slt i32 %2, %3
+            bne %1, 0, loop_0_success, loop_0_fail
+          loop_0_success:
+            %5 = load i32 %result_0_addr
+            %6 = load i32 %i_0_addr
+            %4 = add i32 %5, %6
+            store i32 %4, address %result_0_addr
+            %8 = load i32 %i_0_addr
+            %7 = add i32 %8, 1
+            store i32 %7, address %i_0_addr
+            j loop_0_condition
+          loop_0_fail:
+            %9 = load i32 %result_0_addr
+            ret %9
+        }"
+    ));
+}
+
+#[test]
+fn test_structural_analysis() {
+    let code = r"fn test_condition(i32 %a, i32 %b) -> i32 {
+          test_condition_entry:
+            %a_0_addr = alloca i32
+            store i32 %a, address %a_0_addr
+            %b_0_addr = alloca i32
+            store i32 %b, address %b_0_addr
+            %1 = load i32 %a_0_addr
+            %2 = load i32 %b_0_addr
+            %0 = slt i32 %1, %2
+            bne %0, 0, if_0_success, if_0_fail
+          if_0_success:
+            %3 = load i32 %a_0_addr
+            ret %3
+          if_0_fail:
+            %4 = load i32 %b_0_addr
+            ret %4
+        }";
+    let ir_code = ir::parse(code).unwrap().1;
+    let f = ir_code.as_function_definition();
+    let cfg = ControlFlowGraph::new();
+    let cfg = cfg.bind(f);
+    let folded = FoldedCFG::from_control_flow_graph(&cfg);
+    let result = folded.structural_analysis(&cfg);
+    dbg!(result);
+}
diff --git a/doc/content/tools/ir_breadboard.md b/doc/content/tools/ir_breadboard.md
new file mode 100644
index 0000000..185a66b
--- /dev/null
+++ b/doc/content/tools/ir_breadboard.md
@@ -0,0 +1,409 @@
++++
+title = "IR Breadboard"
+description = "A tool for visualizing different information of given IR"
+date = 2024-06-20T16:21:18.153Z
+updated = 2024-06-20T16:21:18.153Z
+draft = false
++++
+
+<div id="container">
+  <textarea id="code-input"></textarea>
+  <div id="menu">
+    <button id="dump-cfg">Dump CFG</button>
+    <select name="optimize" id="optimize">
+      <option value="MemoryToRegister">MemoryToRegister</option>
+      <option value="RemoveLoadDirectlyAfterStore">RemoveLoadDirectlyAfterStore</option>
+      <option value="RemoveOnlyOnceStore">RemoveOnlyOnceStore</option>
+      <option value="RemoveUnusedRegister">RemoveUnusedRegister</option>
+      <option value="TopologicalSort">TopologicalSort</option>
+    </select>
+    <button id="structural-analysis">Structural Analysis</button>
+  </div>
+  <div id="info">
+    <pre class="hidden" id="output"></pre>
+    <svg class="hidden" id="svg-graph">
+    </svg>
+  </div>
+</div>
+
+<style>
+.hidden {
+  display: none;
+}
+#container {
+  display: flex;
+  align-items: center;
+  margin-left: -24px;
+  margin-right: -24px;
+}
+#code-input {
+  width: 40%;
+  min-height: 100px;
+  height: 800px;
+  font-size: 12px;
+  font-family: 'Zed Mono', 'Fira Code', 'Source Code Pro', 'Courier New', Courier, monospace;
+}
+#menu {
+  width: 18%;
+  min-height: 100px;
+  font-size: 10pt;
+}
+#menu > * {
+  width: 100%;
+}
+#info {
+  width: 40%;
+  height: 800px;
+  overflow: scroll;
+}
+#svg-graph {
+  background: white;
+}
+#svg-graph text {
+  font-size: 10px;
+  stroke: 0;
+  font: 'Zed Mono', 'Fira Code', 'Source Code Pro', 'Courier New', Courier, monospace;
+  fill: #000;
+}
+.clickable {
+  cursor: pointer;
+}
+</style>
+
+<script src="https://dagrejs.github.io/project/dagre/latest/dagre.min.js">
+</script>
+<script type="module">
+import init, { parse, optimize, dump_control_flow_graph, structural_analysis } from "./ir_breadboard.js";
+
+const svgNS = "http://www.w3.org/2000/svg";
+const nodeRadius = 30;
+let expanded = [[0]];
+
+function fill_holes(graph) {
+  let new_nodes = [];
+  const length = graph.node_holes.length + graph.nodes.length;
+  for (let i = 0; i < length; ++i) {
+    if (graph.node_holes[0] === i) {
+      graph.node_holes.shift();
+      new_nodes.push(undefined);
+    } else {
+      new_nodes.push(graph.nodes.shift());
+    }
+  }
+  graph.nodes = new_nodes;
+}
+
+function startsWith(arr, sequence) {
+  if (arr.length < sequence.length) {
+    return false;
+  }
+  for (let i = 0; i < sequence.length; i++) {
+    if (arr[i] !== sequence[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function layoutAll(root_graph, path) {
+  fill_holes(root_graph);
+  let g = new dagre.graphlib.Graph();
+  g.setGraph({marginx: nodeRadius, marginy: nodeRadius});
+  g.setDefaultEdgeLabel(function() { return {}; });
+  root_graph.nodes.forEach((node, index) => {
+    path.push(index);
+
+    if (node !== undefined
+      && expanded.find(it => startsWith(it, path)) === undefined
+      && node.Single === undefined) {
+      // eg.
+      // expanded: [[1, 0, 1, 0], [2, 2, 2]]
+      // path: [1, 0, 1]: expanded, will not reach here
+      // path: [2, 0]   : not expanded, reach heres
+      g.setNode(index, {
+        label: node,
+        width: nodeRadius * 4,
+        height: nodeRadius * 3
+      });
+    } else if (node !== undefined && node.Single !== undefined) {
+      g.setNode(index, {
+        label: node,
+        width: nodeRadius * 2,
+        height: nodeRadius * 2
+      });
+    } else if (node !== undefined && node.If !== undefined) {
+      layoutAll(node.If.content.graph, path);
+      g.setNode(index, {
+        label: node,
+        width: node.If.content.graph.layouted.graph().width,
+        height: node.If.content.graph.layouted.graph().height
+      });
+    } else if (node !== undefined && node.Loop !== undefined) {
+      layoutAll(node.Loop.graph, path);
+      g.setNode(index, {
+        label: node,
+        width: node.Loop.graph.layouted.graph().width,
+        height: node.Loop.graph.layouted.graph().height
+      });
+    } else if (node !== undefined && node.Block !== undefined) {
+      layoutAll(node.Block.graph, path);
+      g.setNode(index, {
+        label: node,
+        width: node.Block.graph.layouted.graph().width,
+        height: node.Block.graph.layouted.graph().height
+      });
+    } else if (node !== undefined) {
+      console.error("unreachable");
+    }
+    path.pop();
+  });
+  root_graph.edges.forEach(edge => {
+    if (edge) {
+      g.setEdge(edge[0], edge[1]);
+    }
+  });
+  dagre.layout(g);
+  root_graph.layouted = g;
+  root_graph.path = path;
+}
+
+function renderExpandableGraph(element, subgraph, path) {
+  subgraph.layouted.edges().forEach(function(e) {
+    let edge = subgraph.layouted.edge(e);
+    let points = edge.points;
+    const path = document.createElementNS(svgNS, "path");
+    path.setAttribute("d", `M ${points[0].x} ${points[0].y} Q${points[1].x} ${points[1].y} ${points[2].x} ${points[2].y}`);
+    path.setAttribute("stroke", "black");
+    path.setAttribute("fill", "transparent");
+    path.setAttribute("style", "stroke:#000; marker-end: url(#arrow);");
+    path.setAttribute("stroke-width", "1.5");
+    let edge_info = subgraph.edges.find(it => it && it.from === parseInt(e.v) && it.to === parseInt(e.w));
+    if (edge_info && edge_info.back) {
+      path.setAttribute('stroke-dasharray', '1.5 1.5');
+    }
+    element.appendChild(path);
+  });
+  subgraph.layouted.nodes().forEach((node, index) => {
+    path.push(parseInt(node));
+    let node_object = subgraph.nodes[node];
+    let node_layout = subgraph.layouted.node(node);
+    let node_subgraph = null;
+    if (node_object.If !== undefined) {
+      node_subgraph = node_object.If.content.graph;
+    } else if (node_object.Loop !== undefined) {
+      node_subgraph = node_object.Loop.graph;
+    } else if (node_object.Block !== undefined) {
+      node_subgraph = node_object.Block.graph;
+    }
+    if (node_object.Single !== undefined) {
+      const circle = document.createElementNS(svgNS, "circle");
+      circle.setAttribute("r", nodeRadius);
+      circle.setAttribute("fill", "red");
+      circle.setAttribute("cx", node_layout.x);
+      circle.setAttribute("cy", node_layout.y);
+      element.appendChild(circle);
+    } else if (node_subgraph !== null && node_subgraph.layouted === undefined) {
+      const group = document.createElementNS(svgNS, "g");
+      group.setAttribute("transform", `translate(${node_layout.x - nodeRadius * 2}, ${node_layout.y - nodeRadius * 1.5})`);
+
+      const rect = document.createElementNS(svgNS, "rect");
+      rect.setAttribute("width", nodeRadius * 4);
+      rect.setAttribute("height", nodeRadius * 3);
+      rect.setAttribute("fill", "red");
+      group.appendChild(rect);
+
+      const textElement = document.createElementNS(svgNS, "text");
+      textElement.setAttribute("x", nodeRadius * 2);
+      textElement.setAttribute("y", nodeRadius * 1.5);
+      textElement.setAttribute("text-anchor", "middle");
+      textElement.setAttribute("dominant-baseline", "central");
+      textElement.setAttribute("pointer-events", "none");
+      textElement.setAttribute("style", "font-size: 20px; fill: white;");
+      const textNode = document.createTextNode("+");
+      textElement.appendChild(textNode);
+      group.appendChild(textElement);
+      group.classList.add("clickable");
+
+      element.appendChild(group);
+      let pathCloned = [...path];
+      element.addEventListener('click', (event) => {
+        event.stopPropagation();
+        let inserted = false;
+        for (let current_expaned of expanded) {
+          if (pathCloned !== undefined && startsWith(pathCloned, current_expaned)) {
+            current_expaned.splice(0, current_expaned.length, ...pathCloned);
+            inserted = true;
+            break;
+          }
+        }
+        if (!inserted) {
+          expanded.push(path);
+        }
+        render_structural_analysis();
+      });
+    } else if (node_subgraph !== null) {
+      const subElement = document.createElementNS(svgNS, "g");
+      subElement.setAttribute("transform", `translate(${node_layout.x - node_subgraph.layouted.graph().width / 2}, ${node_layout.y - node_subgraph.layouted.graph().height / 2})`);
+
+      const rect = document.createElementNS(svgNS, "rect");
+      rect.setAttribute("x", 0);
+      rect.setAttribute("y", 0);
+      rect.setAttribute("width", node_subgraph.layouted.graph().width);
+      rect.setAttribute("height", node_subgraph.layouted.graph().height);
+      rect.setAttribute("fill", "transparent");
+      rect.setAttribute("stroke", "black");
+      rect.setAttribute("stroke-width", "1");
+      subElement.appendChild(rect);
+
+      const closeG = document.createElementNS(svgNS, "g");
+      const closeRect = document.createElementNS(svgNS, "rect");
+      closeRect.setAttribute("x", 0);
+      closeRect.setAttribute("y", 0);
+      closeRect.setAttribute("width", 32);
+      closeRect.setAttribute("height", 24);
+      closeRect.setAttribute("fill", "black");
+
+      const textElement = document.createElementNS(svgNS, "text");
+      textElement.setAttribute("x", 16);
+      textElement.setAttribute("y", 10);
+      textElement.setAttribute("text-anchor", "middle");
+      textElement.setAttribute("dominant-baseline", "central");
+      textElement.setAttribute("pointer-events", "none");
+      textElement.setAttribute("style", "font-size: 20px; fill: white;");
+      const textNode = document.createTextNode("-");
+      textElement.appendChild(textNode);
+
+      closeG.appendChild(closeRect);
+      closeG.appendChild(textElement);
+      closeG.classList.add("clickable");
+
+      let pathCloned = [...path];
+      closeG.addEventListener('click', (event) => {
+        event.stopPropagation();
+        for (let current_expaned of expanded) {
+          if (startsWith(current_expaned, pathCloned)) {
+            while (current_expaned.length !== pathCloned.length) {
+              current_expaned.pop();
+            }
+            current_expaned.pop();
+            break;
+          }
+        }
+        render_structural_analysis();
+      });
+
+      subElement.appendChild(closeG);
+
+      renderExpandableGraph(subElement, node_subgraph, path);
+      element.appendChild(subElement);
+    } else {
+      console.error("unreachable");
+    }
+    path.pop();
+  });
+}
+
+function render_structural_analysis() {
+  const code = document.getElementById('code-input').value;
+  let analysis_result = structural_analysis(code);
+  fill_holes(analysis_result.graph);
+  layoutAll(analysis_result.graph, [0]);
+  const svg = document.getElementById("svg-graph");
+  svg.setAttribute("width", analysis_result.graph.layouted.graph().width);
+  svg.setAttribute("height", analysis_result.graph.layouted.graph().height);
+
+  svg.innerHTML = `<marker id="arrow" markerWidth="5" markerHeight="3.5" refX="4" refY="1.75" orient="auto">
+    <polygon points="0 0, 5 1.75, 0 3.5" />
+  </marker>`;
+  const group = document.createElementNS(svgNS, "g");
+  group.setAttribute("transform", `translate(0, 0)`);
+  renderExpandableGraph(group, analysis_result.graph, [0]);
+  svg.appendChild(group);
+
+  svg.classList.remove('hidden');
+  const textOutput = document.getElementById("output");
+  textOutput.classList.add('hidden');
+}
+
+init().then(() => {
+  document.getElementById('dump-cfg').addEventListener('click', (event) => {
+    event.stopPropagation();
+    const code = document.getElementById('code-input').value;
+    let cfg = dump_control_flow_graph(code);
+    let g = new dagre.graphlib.Graph();
+    g.setGraph({marginx: nodeRadius, marginy: nodeRadius});
+    g.setDefaultEdgeLabel(function() { return {}; });
+    cfg.nodes.forEach((node, index) => {
+      g.setNode(
+        index,
+        {
+          label: index,
+          width: nodeRadius * 2,
+          height: nodeRadius * 2
+        }
+      );
+    });
+    for (let edge of cfg.edges) {
+      g.setEdge(edge.from, edge.to);
+    }
+    dagre.layout(g);
+    const svg = document.getElementById("svg-graph");
+    svg.setAttribute("width", g.graph().width);
+    svg.setAttribute("height", g.graph().height);
+    svg.innerHTML = `<marker id="arrow" markerWidth="5" markerHeight="3.5" refX="4" refY="1.75" orient="auto">
+        <polygon points="0 0, 5 1.75, 0 3.5" />
+    </marker>`;
+    g.edges().forEach(function(e) {
+      let edge = g.edge(e);
+      let points = edge.points;
+      const path = document.createElementNS(svgNS, "path");
+      path.setAttribute("d", `M ${points[0].x} ${points[0].y} Q${points[1].x} ${points[1].y} ${points[2].x} ${points[2].y}`);
+      path.setAttribute("stroke", "black");
+      path.setAttribute("fill", "transparent");
+      path.setAttribute("style", "stroke:#000; marker-end: url(#arrow);");
+      path.setAttribute("stroke-width", "1.5");
+      let edge_info = cfg.edges.find(it => it.from === parseInt(e.v) && it.to === parseInt(e.w));
+      if (edge_info.back) {
+        path.setAttribute('stroke-dasharray', '1.5 1.5');
+      }
+      svg.appendChild(path);
+    });
+    g.nodes().forEach(function(n) {
+      const group = document.createElementNS(svgNS, "g");
+      group.setAttribute("transform", `translate(${g.node(n).x}, ${g.node(n).y})`);
+      const circle = document.createElementNS(svgNS, "circle");
+      circle.setAttribute("r", nodeRadius);
+      circle.setAttribute("fill", "red");
+      group.appendChild(circle);
+
+      const textElement = document.createElementNS(svgNS, "text");
+      textElement.setAttribute("text-anchor", "middle");
+      textElement.setAttribute("dominant-baseline", "central");
+      textElement.setAttribute("fill", "white");
+      const textNode = document.createTextNode(cfg.nodes[parseInt(n)]);
+      textElement.appendChild(textNode);
+      textElement.setAttribute("pointer-events", "none");
+      group.appendChild(textElement);
+
+      svg.appendChild(group);
+    });
+    svg.classList.remove('hidden');
+    const textOutput = document.getElementById("output");
+    textOutput.classList.add('hidden');
+  });
+  document.getElementById('optimize').addEventListener('change', () => {
+    const pass = document.getElementById('optimize').value;
+    const code = document.getElementById('code-input').value;
+    const optimized = optimize(code, pass);
+    const svg = document.getElementById("svg-graph");
+    const textOutput = document.getElementById("output");
+    textOutput.innerText = optimized;
+    svg.classList.add('hidden');
+    textOutput.classList.remove('hidden');
+  });
+  document.getElementById('structural-analysis').addEventListener('click', (event) => {
+    event.stopPropagation();
+    render_structural_analysis();
+  });
+});
+</script>
diff --git a/doc/static/tools/ir-breadboard/.gitignore b/doc/static/tools/ir-breadboard/.gitignore
new file mode 100644
index 0000000..2d6d13a
--- /dev/null
+++ b/doc/static/tools/ir-breadboard/.gitignore
@@ -0,0 +1,4 @@
+*.wasm
+*.js
+*.ts
+package.json
diff --git a/src/backend/riscv/simple_instruction/param_transformer/branch_high.rs b/src/backend/riscv/simple_instruction/param_transformer/branch_high.rs
index 0d8d1c8..5168866 100644
--- a/src/backend/riscv/simple_instruction/param_transformer/branch_high.rs
+++ b/src/backend/riscv/simple_instruction/param_transformer/branch_high.rs
@@ -8,6 +8,12 @@ use bitvec::prelude::*;
 #[derive(Debug, Eq, PartialEq, Clone, Copy)]
 pub struct BranchHigh;
 
+impl Default for BranchHigh {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl BranchHigh {
     pub const fn new() -> Self {
         Self
diff --git a/src/backend/riscv/simple_instruction/param_transformer/branch_low.rs b/src/backend/riscv/simple_instruction/param_transformer/branch_low.rs
index 2ab638d..4fee972 100644
--- a/src/backend/riscv/simple_instruction/param_transformer/branch_low.rs
+++ b/src/backend/riscv/simple_instruction/param_transformer/branch_low.rs
@@ -8,6 +8,12 @@ use bitvec::prelude::*;
 #[derive(Debug, Eq, PartialEq, Clone, Copy)]
 pub struct BranchLow;
 
+impl Default for BranchLow {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl BranchLow {
     pub const fn new() -> Self {
         Self
diff --git a/src/backend/riscv/simple_instruction/param_transformer/csr.rs b/src/backend/riscv/simple_instruction/param_transformer/csr.rs
index b345555..5b177f1 100644
--- a/src/backend/riscv/simple_instruction/param_transformer/csr.rs
+++ b/src/backend/riscv/simple_instruction/param_transformer/csr.rs
@@ -8,6 +8,12 @@ use bitvec::prelude::*;
 #[derive(Debug, Eq, PartialEq, Clone, Copy)]
 pub struct Csr;
 
+impl Default for Csr {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl Csr {
     pub const fn new() -> Self {
         Self
diff --git a/src/backend/riscv/simple_instruction/param_transformer/jal_form.rs b/src/backend/riscv/simple_instruction/param_transformer/jal_form.rs
index 1f5f1a2..6a51a25 100644
--- a/src/backend/riscv/simple_instruction/param_transformer/jal_form.rs
+++ b/src/backend/riscv/simple_instruction/param_transformer/jal_form.rs
@@ -8,6 +8,12 @@ use super::IsParamTransformer;
 #[derive(Debug, Eq, PartialEq, Clone, Copy)]
 pub struct JalForm;
 
+impl Default for JalForm {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl JalForm {
     pub const fn new() -> Self {
         Self
diff --git a/src/backend/riscv/simple_instruction/param_transformer/register.rs b/src/backend/riscv/simple_instruction/param_transformer/register.rs
index aa7e6d4..4f28023 100644
--- a/src/backend/riscv/simple_instruction/param_transformer/register.rs
+++ b/src/backend/riscv/simple_instruction/param_transformer/register.rs
@@ -8,6 +8,12 @@ use super::IsParamTransformer;
 #[derive(Debug, Eq, PartialEq, Clone, Copy)]
 pub struct Register;
 
+impl Default for Register {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl Register {
     pub const fn new() -> Self {
         Self
diff --git a/src/ir/editor/analyzer/control_flow/mod.rs b/src/ir/editor/analyzer/control_flow/mod.rs
index acbf79a..229cf24 100644
--- a/src/ir/editor/analyzer/control_flow/mod.rs
+++ b/src/ir/editor/analyzer/control_flow/mod.rs
@@ -24,7 +24,7 @@ pub use self::scc::BindedScc;
 use super::IsAnalyzer;
 
 mod scc;
-mod structural;
+pub mod structural;
 
 /// [`ControlFlowGraph`] is the control flow graph and related infomation of a function.
 #[derive(Debug)]
@@ -294,7 +294,7 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> {
         )
     }
     pub fn back_edges(&self) -> Vec<(usize, usize)> {
-        self.item.back_edges(&self.bind_on)
+        self.item.back_edges(self.bind_on)
     }
 }
 
diff --git a/src/ir/editor/analyzer/control_flow/structural/mod.rs b/src/ir/editor/analyzer/control_flow/structural/mod.rs
index b18c62e..723c505 100644
--- a/src/ir/editor/analyzer/control_flow/structural/mod.rs
+++ b/src/ir/editor/analyzer/control_flow/structural/mod.rs
@@ -8,13 +8,14 @@ use petgraph::{
     visit::{depth_first_search, DfsEvent, DfsPostOrder, EdgeRef},
     Direction,
 };
+use serde::{Deserialize, Serialize};
 
 use super::BindedControlFlowGraph;
 
 mod selector;
 
-#[derive(Clone, Debug, Default)]
-struct FoldedCFG {
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct FoldedCFG {
     graph: StableDiGraph<RegionNode, Vec<(usize, usize)>, usize>,
     entry: NodeIndex<usize>,
 }
@@ -45,14 +46,14 @@ fn back_edges(c: &FoldedCFG) -> Vec<(NodeIndex<usize>, NodeIndex<usize>)> {
     result
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 struct If {
     content: FoldedCFG,
     on_then: NodeIndex<usize>,
     on_else: Option<NodeIndex<usize>>,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 enum RegionNode {
     Single(usize),
     Loop(FoldedCFG),
@@ -143,7 +144,7 @@ impl FoldedCFG {
             let mut loop_subgraph = FoldedCFG::default();
             let mut node_index_map = HashMap::new(); // id in old => id in new
             for loop_content_index in &smallest_loop_content {
-                let loop_content_node = &content.graph[loop_content_index.clone()];
+                let loop_content_node = &content.graph[*loop_content_index];
                 node_index_map.insert(
                     loop_content_index,
                     loop_subgraph.graph.add_node(loop_content_node.clone()),
@@ -155,16 +156,15 @@ impl FoldedCFG {
             for loop_content_index in &smallest_loop_content {
                 let incoming_edges = content
                     .graph
-                    .edges_directed(loop_content_index.clone(), Direction::Incoming)
-                    .into_iter();
+                    .edges_directed(*loop_content_index, Direction::Incoming);
                 let (intern_edges, extern_edges) = incoming_edges
                     .partition::<Vec<_>, _>(|it| node_index_map.contains_key(&it.source()));
                 for intern_edge in intern_edges {
                     let intern_from = node_index_map.get(&intern_edge.source()).unwrap();
                     let intern_target = node_index_map.get(&intern_edge.target()).unwrap();
                     loop_subgraph.graph.add_edge(
-                        intern_from.clone(),
-                        intern_target.clone(),
+                        *intern_from,
+                        *intern_target,
                         intern_edge.weight().clone(),
                     );
                 }
@@ -177,8 +177,7 @@ impl FoldedCFG {
 
                 let outgoing_edges = content
                     .graph
-                    .edges_directed(loop_content_index.clone(), Direction::Outgoing)
-                    .into_iter();
+                    .edges_directed(*loop_content_index, Direction::Outgoing);
                 let (_intern_edges, extern_edges) = outgoing_edges
                     .partition::<Vec<_>, _>(|it| node_index_map.contains_key(&it.target()));
                 // no need to handle `intern_edges`, because they will be handled of `incoming_edges` on the other side
@@ -228,14 +227,13 @@ impl FoldedCFG {
     }
     pub fn from_control_flow_graph(graph: &BindedControlFlowGraph) -> FoldedCFG {
         let graph_ = graph.graph();
-        let mut result = graph_.map(
+        let result = graph_.map(
             |node_index, _| RegionNode::Single(node_index.index()),
             |edge_id, _| {
                 let (from, to) = graph_.edge_endpoints(edge_id).unwrap();
                 vec![(from.index(), to.index())]
             },
         );
-        result.remove_node(graph.bind_on.content.len().into());
         FoldedCFG {
             graph: result.into(),
             entry: 0.into(),
@@ -250,13 +248,6 @@ fn fold_if_else(
 ) -> bool {
     let (outgoing_node_a, outgoing_node_b) =
         outgoing_nodes_from_condition(region_content, condition, cfg);
-    println!(
-        "fold_if_else on {}, cond={}, a_side={}, b_side={}",
-        region_content,
-        region_content.graph[condition],
-        region_content.graph[outgoing_node_a],
-        region_content.graph[outgoing_node_b],
-    );
     let subregion_node = &region_content.graph[condition];
     let subregion_a = &region_content.graph[outgoing_node_a].clone();
     let mut new_region_content = FoldedCFG {
@@ -269,20 +260,10 @@ fn fold_if_else(
         .graph
         .neighbors_directed(outgoing_node_a, Direction::Outgoing)
         .collect_vec();
-    print!("after_a: ");
-    for aa in &after_a {
-        print!("{}, ", region_content.graph[aa.clone()]);
-    }
-    println!();
     let after_b = region_content
         .graph
         .neighbors_directed(outgoing_node_b, Direction::Outgoing)
         .collect_vec();
-    print!("after_b: ");
-    for bb in &after_b {
-        print!("{}, ", region_content.graph[bb.clone()]);
-    }
-    println!();
     let edges_into_node = region_content
         .graph
         .edges_directed(condition, Direction::Incoming)
@@ -290,9 +271,17 @@ fn fold_if_else(
         .collect_vec();
     if after_a.contains(&outgoing_node_b) {
         // if, without an else
-        println!("if, without an else");
         // the content should only contains the condition (`node`) and the then part (`a`)
         let inserted_subregion_a_index = new_region_content.graph.add_node(subregion_a.clone());
+        let edge_condition_to_a = region_content
+            .graph
+            .find_edge(condition, outgoing_node_a)
+            .unwrap();
+        new_region_content.graph.add_edge(
+            inserted_subregion_node_index,
+            inserted_subregion_a_index,
+            region_content.graph[edge_condition_to_a].clone(),
+        );
         let new_if = If {
             content: new_region_content,
             on_then: inserted_subregion_a_index,
@@ -320,7 +309,7 @@ fn fold_if_else(
         let _new_edge = region_content.graph.add_edge(
             new_node,
             outgoing_node_b,
-            Iterator::chain(condition_to_b.into_iter(), a_to_b.into_iter())
+            Iterator::chain(condition_to_b.iter(), a_to_b)
                 .cloned()
                 .collect(),
         );
@@ -332,21 +321,26 @@ fn fold_if_else(
     } else if after_a.len() == 1 && after_a == after_b {
         let c = after_a[0];
         // if, with an else
-        println!("if, with an else");
         let subregion_b = &region_content.graph[outgoing_node_b].clone();
         let inserted_subregion_a_index = new_region_content.graph.add_node(subregion_a.clone());
         let inserted_subregion_b_index = new_region_content.graph.add_node(subregion_b.clone());
-        println!(
-            "{}: subregion_a={} == {}",
-            inserted_subregion_a_index.index(),
-            subregion_a,
-            &new_region_content.graph[inserted_subregion_a_index],
+        let edge_condition_to_a = region_content
+            .graph
+            .find_edge(condition, outgoing_node_a)
+            .unwrap();
+        let edge_condition_to_b = region_content
+            .graph
+            .find_edge(condition, outgoing_node_b)
+            .unwrap();
+        new_region_content.graph.add_edge(
+            inserted_subregion_node_index,
+            inserted_subregion_a_index,
+            region_content.graph[edge_condition_to_a].clone(),
         );
-        println!(
-            "{}: subregion_b={} == {}",
-            inserted_subregion_b_index.index(),
-            subregion_b,
-            &new_region_content.graph[inserted_subregion_b_index],
+        new_region_content.graph.add_edge(
+            inserted_subregion_node_index,
+            inserted_subregion_b_index,
+            region_content.graph[edge_condition_to_b].clone(),
         );
         let new_if = If {
             content: new_region_content,
@@ -372,7 +366,7 @@ fn fold_if_else(
             )
             .collect_tuple()
             .unwrap();
-        let new_weight = Iterator::chain(a_to_c.weight().into_iter(), b_to_c.weight().into_iter())
+        let new_weight = Iterator::chain(a_to_c.weight().iter(), b_to_c.weight())
             .cloned()
             .collect();
         let _new_edge = region_content.graph.add_edge(new_node, c, new_weight);
@@ -436,19 +430,19 @@ fn single_entry_single_exit_nodes(
     // detect up
     while content
         .graph
-        .neighbors_directed(current_looking_at_node.into(), Direction::Incoming)
+        .neighbors_directed(current_looking_at_node, Direction::Incoming)
         .count()
         == 1
         && content
             .graph
-            .neighbors_directed(current_looking_at_node.into(), Direction::Outgoing)
+            .neighbors_directed(current_looking_at_node, Direction::Outgoing)
             .count()
             == 1
     {
         result.push(current_looking_at_node);
         current_looking_at_node = content
             .graph
-            .neighbors_directed(current_looking_at_node.into(), Direction::Incoming)
+            .neighbors_directed(current_looking_at_node, Direction::Incoming)
             .exactly_one()
             .unwrap();
     }
@@ -458,12 +452,12 @@ fn single_entry_single_exit_nodes(
     // detect down
     while content
         .graph
-        .neighbors_directed(current_looking_at_node.into(), Direction::Incoming)
+        .neighbors_directed(current_looking_at_node, Direction::Incoming)
         .count()
         == 1
         && content
             .graph
-            .neighbors_directed(current_looking_at_node.into(), Direction::Outgoing)
+            .neighbors_directed(current_looking_at_node, Direction::Outgoing)
             .count()
             == 1
     {
@@ -742,4 +736,34 @@ mod tests {
         let saed = FoldedCFG::structural_analysis(folded_cfg, &binded);
         println!("{}", saed);
     }
+
+    #[test]
+    fn test_structual_analysis4() {
+        let code = r"fn test_condition(i32 %a, i32 %b) -> i32 {
+          test_condition_entry:
+            %a_0_addr = alloca i32
+            store i32 %a, address %a_0_addr
+            %b_0_addr = alloca i32
+            store i32 %b, address %b_0_addr
+            %1 = load i32 %a_0_addr
+            %2 = load i32 %b_0_addr
+            %0 = slt i32 %1, %2
+            bne %0, 0, if_0_success, if_0_fail
+          if_0_success:
+            %3 = load i32 %a_0_addr
+            ret %3
+          if_0_fail:
+            %4 = load i32 %b_0_addr
+            ret %4
+          if_0_end:
+            ret
+        }";
+        let ir_code = ir::parse(code).unwrap().1;
+        let f = ir_code.as_function_definition();
+        let cfg = ControlFlowGraph::new();
+        let cfg = cfg.bind(f);
+        let folded = FoldedCFG::from_control_flow_graph(&cfg);
+        let result = folded.structural_analysis(&cfg);
+        dbg!(result);
+    }
 }
diff --git a/src/ir/editor/analyzer/mod.rs b/src/ir/editor/analyzer/mod.rs
index 0b3b0d8..ef91459 100644
--- a/src/ir/editor/analyzer/mod.rs
+++ b/src/ir/editor/analyzer/mod.rs
@@ -8,7 +8,7 @@ pub use self::{
 };
 use super::action::Action;
 
-mod control_flow;
+pub mod control_flow;
 mod memory_usage;
 pub mod register_usage;
 
diff --git a/src/ir/function/basic_block.rs b/src/ir/function/basic_block.rs
index e95c009..ec6923e 100644
--- a/src/ir/function/basic_block.rs
+++ b/src/ir/function/basic_block.rs
@@ -1,3 +1,7 @@
+use super::{
+    statement::{self, IRStatement},
+    IsIRStatement,
+};
 use crate::{
     ir::RegisterName,
     utility::{data_type::Type, parsing},
@@ -11,15 +15,11 @@ use nom::{
     sequence::{pair, tuple},
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt;
 
-use super::{
-    statement::{self, IRStatement},
-    IsIRStatement,
-};
-
 /// A basic block.
-#[derive(Debug, Eq, PartialEq, Clone, Hash, Default)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Default, Serialize, Deserialize)]
 pub struct BasicBlock {
     /// Name of the basic block.
     pub name: Option<String>,
diff --git a/src/ir/function/mod.rs b/src/ir/function/mod.rs
index 88d874d..b73b8c9 100644
--- a/src/ir/function/mod.rs
+++ b/src/ir/function/mod.rs
@@ -15,6 +15,7 @@ use nom::{
     IResult,
 };
 use parameter::Parameter;
+use serde::{Deserialize, Serialize};
 use statement::*;
 use std::{
     fmt, mem,
@@ -106,7 +107,7 @@ impl<'a> Iter<'a> {
     }
 }
 
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
 pub struct FunctionHeader {
     /// Name of the function.
     pub name: String,
@@ -117,7 +118,7 @@ pub struct FunctionHeader {
 }
 
 /// [`FunctionDefinition`] represents a function definition.
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
 pub struct FunctionDefinition {
     pub header: FunctionHeader,
     /// Basic blocks of the function.
@@ -292,3 +293,16 @@ pub fn formalize(mut function: FunctionDefinition) -> FunctionDefinition {
     }
     function
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    // todo: more tests
+    #[test]
+    fn test_parse() {
+        let code = r"fn main() -> () {
+              %0 = add i32 1, 2
+            }";
+        assert!(parse(code).is_ok());
+    }
+}
diff --git a/src/ir/function/parameter.rs b/src/ir/function/parameter.rs
index b5fcd93..03e663c 100644
--- a/src/ir/function/parameter.rs
+++ b/src/ir/function/parameter.rs
@@ -5,9 +5,9 @@ use crate::{
     ir::{quantity::local, RegisterName},
     utility::data_type::{self, Type},
 };
-
+use serde::{Deserialize, Serialize};
 /// [`Parameter`] represents a function's parameter.
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
 pub struct Parameter {
     /// Name of the parameter.
     pub name: RegisterName,
diff --git a/src/ir/function/statement/alloca.rs b/src/ir/function/statement/alloca.rs
index 556d8e5..fe47999 100644
--- a/src/ir/function/statement/alloca.rs
+++ b/src/ir/function/statement/alloca.rs
@@ -12,10 +12,10 @@ use nom::{
     sequence::tuple,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt::{self, Display, Formatter};
-
 /// [`Alloca`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Alloca {
     /// Local variable, pointing to the space allocated on the stack.
     pub to: RegisterName,
diff --git a/src/ir/function/statement/branch.rs b/src/ir/function/statement/branch.rs
index 74fde31..7cfd77c 100644
--- a/src/ir/function/statement/branch.rs
+++ b/src/ir/function/statement/branch.rs
@@ -1,3 +1,4 @@
+use super::calculate::{binary::BinaryOperation, BinaryCalculate};
 use crate::{
     ir::{
         function::IsIRStatement,
@@ -17,15 +18,14 @@ use nom::{
     sequence::tuple,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::{
     fmt,
     fmt::{Display, Formatter},
 };
 
-use super::calculate::{binary::BinaryOperation, BinaryCalculate};
-
 /// Enum of all possible branch types.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub enum BranchType {
     EQ,
     NE,
@@ -49,7 +49,7 @@ fn branch_type(code: &str) -> IResult<&str, BranchType> {
 }
 
 /// [`Branch`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
 pub struct Branch {
     /// Type of the branch.
     pub branch_type: BranchType,
diff --git a/src/ir/function/statement/calculate/binary.rs b/src/ir/function/statement/calculate/binary.rs
index 53b1ae4..34a1a80 100644
--- a/src/ir/function/statement/calculate/binary.rs
+++ b/src/ir/function/statement/calculate/binary.rs
@@ -19,10 +19,10 @@ use nom::{
     IResult,
 };
 use phf::phf_map;
+use serde::{Deserialize, Serialize};
 use std::fmt;
-
 /// [`BinaryOperation`] represents a binary operation operator.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub enum BinaryOperation {
     Add,
     LessThan,
@@ -104,7 +104,7 @@ fn binary_operation(code: &str) -> IResult<&str, BinaryOperation> {
 }
 
 /// [`BinaryCalculate`] represents a binary operation statement.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
 pub struct BinaryCalculate {
     pub operation: BinaryOperation,
     pub operand1: Quantity,
diff --git a/src/ir/function/statement/calculate/unary.rs b/src/ir/function/statement/calculate/unary.rs
index 90e5c7a..d839c62 100644
--- a/src/ir/function/statement/calculate/unary.rs
+++ b/src/ir/function/statement/calculate/unary.rs
@@ -19,10 +19,10 @@ use nom::{
     IResult,
 };
 use phf::phf_map;
+use serde::{Deserialize, Serialize};
 use std::fmt;
-
 /// [`UnaryOperation`] represents a unary operation operator.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub enum UnaryOperation {
     Neg,
     Not,
@@ -51,7 +51,7 @@ impl fmt::Display for UnaryOperation {
 }
 
 /// [`UnaryCalculate`] represents the result of a unary operator.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
 pub struct UnaryCalculate {
     pub operation: UnaryOperation,
     pub operand: Quantity,
diff --git a/src/ir/function/statement/call.rs b/src/ir/function/statement/call.rs
index 38ddd6a..474f309 100644
--- a/src/ir/function/statement/call.rs
+++ b/src/ir/function/statement/call.rs
@@ -19,10 +19,10 @@ use nom::{
     sequence::{delimited, tuple},
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt::{self, Display, Formatter};
-
 /// [`Call`] instruction.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct Call {
     /// Where to store the result of the call.
     pub to: Option<RegisterName>,
diff --git a/src/ir/function/statement/jump.rs b/src/ir/function/statement/jump.rs
index b8986cb..74b4d1a 100644
--- a/src/ir/function/statement/jump.rs
+++ b/src/ir/function/statement/jump.rs
@@ -5,13 +5,13 @@ use crate::{
 use nom::{
     bytes::complete::tag, character::complete::space1, combinator::map, sequence::tuple, IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::{
     fmt,
     fmt::{Display, Formatter},
 };
-
 /// [`Jump`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Jump {
     pub label: String,
 }
diff --git a/src/ir/function/statement/load.rs b/src/ir/function/statement/load.rs
index 740700a..ed38a3a 100644
--- a/src/ir/function/statement/load.rs
+++ b/src/ir/function/statement/load.rs
@@ -14,9 +14,9 @@ use nom::{
     sequence::tuple,
     IResult,
 };
-
+use serde::{Deserialize, Serialize};
 /// [`Load`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Load {
     pub to: RegisterName,
     pub data_type: Type,
diff --git a/src/ir/function/statement/load_field.rs b/src/ir/function/statement/load_field.rs
index fd2103e..05be7de 100644
--- a/src/ir/function/statement/load_field.rs
+++ b/src/ir/function/statement/load_field.rs
@@ -1,3 +1,4 @@
+use super::Load;
 use crate::{
     ast::{
         self,
@@ -21,12 +22,11 @@ use nom::{
     sequence::{delimited, tuple},
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt;
 
-use super::Load;
-
 /// [`LoadField`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct LoadField {
     /// Where to store the result of the load.
     pub target: RegisterName,
diff --git a/src/ir/function/statement/mod.rs b/src/ir/function/statement/mod.rs
index 4e88fef..f6c50d3 100644
--- a/src/ir/function/statement/mod.rs
+++ b/src/ir/function/statement/mod.rs
@@ -3,6 +3,7 @@ use std::fmt;
 use enum_dispatch::enum_dispatch;
 use nom::{branch::alt, combinator::map, IResult};
 use paste::paste;
+use serde::{Deserialize, Serialize};
 
 /// Data structure, parser and ir generator for `alloca` statement.
 mod alloca;
@@ -27,6 +28,10 @@ pub(crate) mod set_field;
 /// Data structure, parser and ir generator for `store` statement.
 mod store;
 
+use crate::{
+    ir::{quantity::Quantity, RegisterName},
+    utility::data_type::Type,
+};
 pub use alloca::Alloca;
 pub use branch::Branch;
 pub use calculate::{BinaryCalculate, UnaryCalculate};
@@ -39,11 +44,6 @@ pub use ret::Ret;
 pub use set_field::SetField;
 pub use store::Store;
 
-use crate::{
-    ir::{quantity::Quantity, RegisterName},
-    utility::data_type::Type,
-};
-
 /// This trait should be implemented for all IRStatements
 #[enum_dispatch]
 pub trait IsIRStatement {
@@ -54,7 +54,7 @@ pub trait IsIRStatement {
 
 /// A statement in a function.
 #[enum_dispatch(IsIRStatement)]
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub enum IRStatement {
     Phi,
     Alloca,
diff --git a/src/ir/function/statement/phi.rs b/src/ir/function/statement/phi.rs
index e9a4aaa..7f17592 100644
--- a/src/ir/function/statement/phi.rs
+++ b/src/ir/function/statement/phi.rs
@@ -17,10 +17,10 @@ use nom::{
     sequence::{delimited, tuple},
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt;
-
 /// [`Phi`]'s source.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
 pub struct PhiSource {
     pub value: Quantity,
     pub block: String,
@@ -50,7 +50,7 @@ fn parse_phi_source(code: &str) -> IResult<&str, PhiSource> {
 }
 
 /// [`Phi`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Phi {
     /// Where to store the result of the phi.
     pub to: RegisterName,
diff --git a/src/ir/function/statement/ret.rs b/src/ir/function/statement/ret.rs
index 1196e92..095074a 100644
--- a/src/ir/function/statement/ret.rs
+++ b/src/ir/function/statement/ret.rs
@@ -13,10 +13,10 @@ use nom::{
     sequence::tuple,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt;
-
 /// [`Ret`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Ret {
     pub value: Option<Quantity>,
 }
diff --git a/src/ir/function/statement/set_field.rs b/src/ir/function/statement/set_field.rs
index 9d81b7c..175deb8 100644
--- a/src/ir/function/statement/set_field.rs
+++ b/src/ir/function/statement/set_field.rs
@@ -20,9 +20,9 @@ use nom::{
     sequence::{delimited, tuple},
     IResult,
 };
-
+use serde::{Deserialize, Serialize};
 /// [`SetField`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct SetField {
     /// Where to store the result.
     pub target: RegisterName,
diff --git a/src/ir/function/statement/store.rs b/src/ir/function/statement/store.rs
index e609555..be775fb 100644
--- a/src/ir/function/statement/store.rs
+++ b/src/ir/function/statement/store.rs
@@ -13,10 +13,10 @@ use nom::{
     sequence::tuple,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt;
-
 /// [`Store`] instruction.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Store {
     /// Type of the value to store.
     pub data_type: Type,
diff --git a/src/ir/global_definition.rs b/src/ir/global_definition.rs
index 2d7eb33..e64b5b9 100644
--- a/src/ir/global_definition.rs
+++ b/src/ir/global_definition.rs
@@ -17,9 +17,10 @@ use nom::{
     sequence::tuple,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 
 /// [`GlobalDefinition`] represents a global variable definition.
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
 pub struct GlobalDefinition {
     /// Name of the global variable.
     pub name: GlobalVariableName,
diff --git a/src/ir/integer_literal.rs b/src/ir/integer_literal.rs
index b8f297b..9fecdc9 100644
--- a/src/ir/integer_literal.rs
+++ b/src/ir/integer_literal.rs
@@ -2,9 +2,10 @@ use std::fmt;
 
 use crate::{ast, utility::parsing};
 use nom::{combinator::map, IResult};
+use serde::{Deserialize, Serialize};
 
 /// An integer literal.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct IntegerLiteral(pub i64);
 
 impl From<i64> for IntegerLiteral {
diff --git a/src/ir/mod.rs b/src/ir/mod.rs
index f30f5c2..24b7541 100644
--- a/src/ir/mod.rs
+++ b/src/ir/mod.rs
@@ -4,6 +4,7 @@ use std::{
 };
 
 use enum_dispatch::enum_dispatch;
+use serde::{Deserialize, Serialize};
 
 /// Data structure, parser and ir generator for functions.
 pub mod function;
@@ -35,13 +36,23 @@ pub use editor::analyzer;
 
 /// The root nodes of IR.
 #[enum_dispatch]
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum IR {
     TypeDefinition,
     FunctionDefinition,
     GlobalDefinition,
 }
 
+impl IR {
+    pub fn as_function_definition(&self) -> &FunctionDefinition {
+        if let IR::FunctionDefinition(f) = self {
+            f
+        } else {
+            panic!("This is not a function definition!")
+        }
+    }
+}
+
 impl fmt::Display for IR {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
@@ -167,4 +178,15 @@ pub fn from_ast(ast: &Ast) -> Vec<IR> {
         .collect()
 }
 
-// todo: tests
+#[cfg(test)]
+mod tests {
+    use super::*;
+    // todo: more tests
+    #[test]
+    fn test_parse() {
+        let code = r"fn main() -> () {
+              %0 = add i32 1, 2
+            }";
+        assert!(parse(code).is_ok());
+    }
+}
diff --git a/src/ir/optimize/pass/fix_irreducible/mod.rs b/src/ir/optimize/pass/fix_irreducible/mod.rs
index 7a58eef..53e3d8c 100644
--- a/src/ir/optimize/pass/fix_irreducible/mod.rs
+++ b/src/ir/optimize/pass/fix_irreducible/mod.rs
@@ -1,10 +1,10 @@
+use itertools::Itertools;
+use serde::{Deserialize, Serialize};
 use std::{
     collections::{BTreeSet, HashMap, HashSet},
     hash::Hash,
 };
 
-use itertools::Itertools;
-
 use crate::{
     ir::{
         analyzer::BindedControlFlowGraph,
@@ -20,7 +20,7 @@ use crate::{
 
 use super::{IsPass, Pass};
 
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub struct FixIrreducible;
 
 #[derive(Debug, Hash, PartialEq, Eq, Clone)]
@@ -184,7 +184,7 @@ fn generate_edit_plan(
     // Generate all the branches, include the last one
     let mut branches = phis
         .iter()
-        .zip(origin_targets.into_iter())
+        .zip(origin_targets)
         .tuple_windows()
         .map(
             |((depend_on_phi_result, target_block_index), (_, next_target_block_index))| Branch {
@@ -287,7 +287,7 @@ fn execute_edit_plan(function: &mut FunctionDefinition, plan: EditPlan) {
     for fix_plan in plan.fix_other_block_plan {
         fix_other_block(function, &first_guard_block_name, &plan.scc_id, fix_plan);
     }
-    function.content.extend(guard_blocks.into_iter());
+    function.content.extend(guard_blocks);
 }
 
 fn generate_origin_target_to_source_map(
diff --git a/src/ir/optimize/pass/memory_to_register.rs b/src/ir/optimize/pass/memory_to_register.rs
index 05babae..7e831fe 100644
--- a/src/ir/optimize/pass/memory_to_register.rs
+++ b/src/ir/optimize/pass/memory_to_register.rs
@@ -1,7 +1,5 @@
 use std::collections::HashMap;
 
-use itertools::Itertools;
-
 use crate::{
     ir::{
         editor::{analyzer, Editor},
@@ -11,6 +9,8 @@ use crate::{
     },
     utility::data_type::Type,
 };
+use itertools::Itertools;
+use serde::{Deserialize, Serialize};
 
 use super::{
     remove_only_once_store::RemoveOnlyOnceStore, remove_unused_register::RemoveUnusedRegister,
@@ -19,7 +19,7 @@ use super::{
 
 /// [`MemoryToRegister`] is a pass that convert memory access to register access.
 /// It is similar to LLVM's [`mem2reg`](https://llvm.org/docs/Passes.html#mem2reg-promote-memory-to-register).
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub struct MemoryToRegister;
 
 impl IsPass for MemoryToRegister {
diff --git a/src/ir/optimize/pass/mod.rs b/src/ir/optimize/pass/mod.rs
index 533dfe2..c1eb594 100644
--- a/src/ir/optimize/pass/mod.rs
+++ b/src/ir/optimize/pass/mod.rs
@@ -11,9 +11,9 @@ use memory_to_register::MemoryToRegister;
 use remove_load_directly_after_store::RemoveLoadDirectlyAfterStore;
 use remove_only_once_store::RemoveOnlyOnceStore;
 use remove_unused_register::RemoveUnusedRegister;
-pub use topological_sort::TopologicalSort;
-
+use serde::{Deserialize, Serialize};
 use std::str::FromStr;
+pub use topological_sort::TopologicalSort;
 /// This trait should be implemented by all passes which can do optimizing on ir function.
 #[enum_dispatch]
 pub trait IsPass {
@@ -28,7 +28,7 @@ pub trait IsPass {
 
 /// All passes which can do optimizing on ir function.
 #[enum_dispatch(IsPass)]
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub enum Pass {
     RemoveUnusedRegister,
     RemoveOnlyOnceStore,
diff --git a/src/ir/optimize/pass/remove_load_directly_after_store.rs b/src/ir/optimize/pass/remove_load_directly_after_store.rs
index 45b22b1..ee77eb1 100644
--- a/src/ir/optimize/pass/remove_load_directly_after_store.rs
+++ b/src/ir/optimize/pass/remove_load_directly_after_store.rs
@@ -1,11 +1,11 @@
-use crate::ir::editor::Editor;
-
 use super::IsPass;
+use crate::ir::editor::Editor;
+use serde::{Deserialize, Serialize};
 
 /// This pass will remove all load instructions which are
 /// - in same block with a store instruction
 /// - after the store instruction.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub struct RemoveLoadDirectlyAfterStore;
 
 impl IsPass for RemoveLoadDirectlyAfterStore {
diff --git a/src/ir/optimize/pass/remove_only_once_store.rs b/src/ir/optimize/pass/remove_only_once_store.rs
index bf26a78..edc0206 100644
--- a/src/ir/optimize/pass/remove_only_once_store.rs
+++ b/src/ir/optimize/pass/remove_only_once_store.rs
@@ -1,10 +1,10 @@
 use super::IsPass;
-
+use serde::{Deserialize, Serialize};
 /// This pass will
 /// - remove all store statements which is the only one store to a variable
 /// - remove the load statements to the variable
 /// - replace all usage of the load results to the source of the store
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub struct RemoveOnlyOnceStore;
 
 impl IsPass for RemoveOnlyOnceStore {
diff --git a/src/ir/optimize/pass/remove_unused_register.rs b/src/ir/optimize/pass/remove_unused_register.rs
index 1fad791..e11d9ba 100644
--- a/src/ir/optimize/pass/remove_unused_register.rs
+++ b/src/ir/optimize/pass/remove_unused_register.rs
@@ -1,9 +1,8 @@
-use crate::ir::editor;
-
 use super::IsPass;
-use crate::ir::editor::analyzer::register_usage::RegisterDefinePosition;
+use crate::ir::{editor, editor::analyzer::register_usage::RegisterDefinePosition};
+use serde::{Deserialize, Serialize};
 /// This pass will remove the register which are defined but not used.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub struct RemoveUnusedRegister;
 
 impl IsPass for RemoveUnusedRegister {
diff --git a/src/ir/optimize/pass/topological_sort.rs b/src/ir/optimize/pass/topological_sort.rs
index e0208f9..32d524a 100644
--- a/src/ir/optimize/pass/topological_sort.rs
+++ b/src/ir/optimize/pass/topological_sort.rs
@@ -1,7 +1,7 @@
-use std::mem;
-
 use itertools::Itertools;
 use petgraph::prelude::*;
+use serde::{Deserialize, Serialize};
+use std::mem;
 
 use crate::ir::{
     analyzer::{BindedControlFlowGraph, IsAnalyzer},
@@ -10,7 +10,7 @@ use crate::ir::{
 
 use super::IsPass;
 
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
 pub struct TopologicalSort;
 
 impl IsPass for TopologicalSort {
diff --git a/src/ir/quantity/global.rs b/src/ir/quantity/global.rs
index 27f59d8..35e6412 100644
--- a/src/ir/quantity/global.rs
+++ b/src/ir/quantity/global.rs
@@ -1,9 +1,10 @@
 use crate::utility::parsing;
 use nom::{bytes::complete::tag, combinator::map, sequence::pair, IResult};
+use serde::{Deserialize, Serialize};
 use std::fmt::{self, Display, Formatter};
 
 /// [`GlobalVariableName`] represents a global variable's name.
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct GlobalVariableName(pub String);
 
 impl Display for GlobalVariableName {
diff --git a/src/ir/quantity/local.rs b/src/ir/quantity/local.rs
index f3690d4..6ae86b4 100644
--- a/src/ir/quantity/local.rs
+++ b/src/ir/quantity/local.rs
@@ -7,10 +7,11 @@ use nom::{
     sequence::pair,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt::{self, Display, Formatter};
 
 /// [`RegisterName`] represents a local variable's name.
-#[derive(Debug, Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, PartialOrd, Ord, Serialize, Deserialize)]
 pub struct RegisterName(pub String);
 
 impl Display for RegisterName {
diff --git a/src/ir/quantity/mod.rs b/src/ir/quantity/mod.rs
index d87cd52..8cc075f 100644
--- a/src/ir/quantity/mod.rs
+++ b/src/ir/quantity/mod.rs
@@ -5,15 +5,15 @@ pub use crate::ir::quantity::{global::GlobalVariableName, local::RegisterName};
 use crate::utility::parsing;
 use enum_dispatch::enum_dispatch;
 use nom::{branch::alt, combinator::map, IResult};
+use serde::{Deserialize, Serialize};
 use std::fmt::{self, Display, Formatter};
-
 /// Tag trait for [`Quantity`].
 #[enum_dispatch]
 trait IsQuantity {}
 
 /// [`Quantity`] represents a variable (global or local) or a constant value
 #[enum_dispatch(IsQuantity)]
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
 pub enum Quantity {
     RegisterName,
     GlobalVariableName,
diff --git a/src/ir/type_definition.rs b/src/ir/type_definition.rs
index 80359f5..301c55b 100644
--- a/src/ir/type_definition.rs
+++ b/src/ir/type_definition.rs
@@ -11,10 +11,11 @@ use nom::{
     sequence::{delimited, tuple},
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::{collections::HashMap, fmt};
 
 /// [`TypeDefinition`] represents definition of a struct.
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
 pub struct TypeDefinition {
     pub name: String,
     pub fields: Vec<Type>,
diff --git a/src/utility/data_type.rs b/src/utility/data_type.rs
index 509c6ba..bd8b792 100644
--- a/src/utility/data_type.rs
+++ b/src/utility/data_type.rs
@@ -7,10 +7,11 @@ use nom::{
     sequence::pair,
     IResult,
 };
+use serde::{Deserialize, Serialize};
 use std::fmt;
 
 /// An integer type
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub struct Integer {
     /// Whether the integer is signed.
     pub signed: bool,
@@ -19,7 +20,7 @@ pub struct Integer {
 }
 
 /// Type in IR
-#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
 pub enum Type {
     Integer(Integer),
     StructRef(String),