// createListPrim creates a list primitive containing a slice of Go statements // based on the identified subgraph, its node pair mapping and its basic blocks. // The new control flow primitive conceptually represents a basic block with the // given name. // // Contents of "list.dot": // // digraph list { // entry [label="entry"] // exit [label="exit"] // entry->exit // } func createListPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) { // Locate graph nodes. nameEntry, ok := m["entry"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "entry"`) } nameExit, ok := m["exit"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "exit"`) } bbEntry, ok := bbs[nameEntry] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameEntry) } bbExit, ok := bbs[nameExit] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameExit) } // Create and return new primitive. // // entry // exit stmts := append(bbEntry.Stmts(), bbExit.Stmts()...) prim := &primitive{ name: newName, stmts: stmts, term: bbExit.Term(), } return prim, nil }
// NewSubGraph returns a new subgraph based on graph with a dedicated entry and // exit node. The entry and exit nodes are identified using the node "label" // attribute, e.g. // // digraph if { // A->B [label="true"] // A->C [label="false"] // B->C // A [label="entry"] // B // C [label="exit"] // } func NewSubGraph(graph *dot.Graph) (*SubGraph, error) { sub := &SubGraph{Graph: graph} // Locate entry and exit nodes. var hasEntry, hasExit bool for _, node := range graph.Nodes.Nodes { label, ok := node.Attrs["label"] if !ok { continue } switch label { case "entry": if hasEntry { return nil, errutil.Newf(`redefinition of node with "entry" label; previous node %q, new node %q`, sub.entry, node.Name) } sub.entry = node.Name hasEntry = true case "exit": if hasExit { return nil, errutil.Newf(`redefinition of node with "exit" label; previous node %q, new node %q`, sub.exit, node.Name) } sub.exit = node.Name hasExit = true } } if !hasEntry { return nil, errutil.New(`unable to locate node with "entry" label`) } if !hasExit { return nil, errutil.New(`unable to locate node with "exit" label`) } return sub, nil }
// createPostLoopPrim creates a post-test loop primitive based on the identified // subgraph, its node pair mapping and its basic blocks. The new control flow // primitive conceptually represents a basic block with the given name. // // Contents of "post_loop.dot": // // digraph post_loop { // cond [label="entry"] // exit [label="exit"] // cond->cond [label="true"] // cond->exit [label="false"] // } func createPostLoopPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) { // Locate graph nodes. nameCond, ok := m["cond"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "cond"`) } nameExit, ok := m["exit"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "exit"`) } bbCond, ok := bbs[nameCond] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameCond) } bbExit, ok := bbs[nameExit] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameExit) } // Create and return new primitive. // // for { // cond_stmts // if !cond { // break // } // } // exit // Create if-statement. cond, _, _, err := getBrCond(bbCond.Term()) if err != nil { return nil, errutil.Err(err) } ifStmt := &ast.IfStmt{ Cond: &ast.UnaryExpr{Op: token.NOT, X: cond}, // negate condition Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, } // Create for-loop. body := bbCond.Stmts() body = append(body, ifStmt) forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{List: body}, } // Create primitive. stmts := []ast.Stmt{forStmt} stmts = append(stmts, bbExit.Stmts()...) prim := &primitive{ name: newName, stmts: stmts, term: bbExit.Term(), } return prim, nil }
// createIfPrim creates an if-statement primitive based on the identified // subgraph, its node pair mapping and its basic blocks. The new control flow // primitive conceptually represents a basic block with the given name. // // Contents of "if.dot": // // digraph if { // cond [label="entry"] // body // exit [label="exit"] // cond->body [label="true"] // cond->exit [label="false"] // body->exit // } func createIfPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) { // Locate graph nodes. nameCond, ok := m["cond"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "cond"`) } nameBody, ok := m["body"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "body"`) } nameExit, ok := m["exit"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "exit"`) } bbCond, ok := bbs[nameCond] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameCond) } bbBody, ok := bbs[nameBody] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameBody) } bbExit, ok := bbs[nameExit] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameExit) } // Create and return new primitive. // // cond_stmts // if cond { // body // } // exit // Create if-statement. cond, _, _, err := getBrCond(bbCond.Term()) if err != nil { return nil, errutil.Err(err) } ifStmt := &ast.IfStmt{ Cond: cond, Body: &ast.BlockStmt{List: bbBody.Stmts()}, } // Create primitive. stmts := append(bbCond.Stmts(), ifStmt) stmts = append(stmts, bbExit.Stmts()...) prim := &primitive{ name: newName, stmts: stmts, term: bbExit.Term(), } return prim, nil }
// solveBrute tries to solve the node pair equation through brute force. It // recursively locates and attempts to solve the easiest node pair (i.e. the one // with the fewest number of candidates) until the equation is solved, or until // all potential solutions have been exhausted. func (eq *equation) solveBrute(graph *dot.Graph, sub *graphs.SubGraph) (m map[string]string, err error) { if eq.isValid(graph, sub) { return eq.m, nil } sname, err := eq.easiest() if err != nil { return nil, err } // Sort candidates to make the algorithm deterministic. candidates := make([]string, 0, len(eq.c[sname])) for gname := range eq.c[sname] { candidates = append(candidates, gname) } sort.Strings(candidates) for _, gname := range candidates { dup := eq.dup() err = dup.setPair(sname, gname) if err != nil { continue } m, err := dup.solveBrute(graph, sub) if err != nil { continue } return m, nil } return nil, errutil.New("unable to locate node pair mapping") }
func ExampleNew() { errutil.UseColor = false err := errutil.New("failure.") fmt.Println(err) // Output: // github.com/mewkiz/pkg/errutil_test.ExampleNew (err_test.go:11): failure. }
// parseOperand converts the provided LLVM IR operand into an equivalent Go AST // expression node (a basic literal, a composite literal or an identifier). // // Syntax: // i32 1 // %foo = ... func parseOperand(op llvm.Value) (ast.Expr, error) { // TODO: Support *BasicLit, *CompositeLit. // Parse and validate tokens. tokens, err := getTokens(op) if err != nil { return nil, err } if len(tokens) < 2 { // TODO: Remove debug output. op.Dump() return nil, errutil.Newf("unable to parse operand; expected 2 >= tokens, got %d", len(tokens)) } // TODO: Add support for operand of other types than int. // TODO: Parse type. // Create and return a constant operand. // i32 42 if tokens[0].Kind == lltoken.Type { switch tok := tokens[1]; tok.Kind { case lltoken.Int: return &ast.BasicLit{Kind: token.INT, Value: tok.Val}, nil case lltoken.LocalVar: return getIdent(tok) default: return nil, errutil.Newf("support for LLVM IR token kind %v not yet implemented", tok.Kind) } } // Create and return a variable operand. // %foo = ... if tokens[1].Kind == lltoken.Equal { switch tok := tokens[0]; tok.Kind { case lltoken.LocalVar, lltoken.LocalID: // %foo // %42 return getIdent(tok) default: return nil, errutil.Newf("support for LLVM IR token kind %v not yet implemented", tok.Kind) } } return nil, errutil.New("support for LLVM IR operand not yet implemented") }
// restructure attempts to create a structured control flow for a function based // on the provided control flow graph (which contains one node per basic block) // and the function's basic blocks. It does so by repeatedly locating and // merging structured subgraphs into single nodes until the entire graph is // reduced into a single node or no structured subgraphs may be located. func restructure(graph *dot.Graph, bbs map[string]BasicBlock, hprims []*xprimitive.Primitive) (*ast.BlockStmt, error) { for _, hprim := range hprims { subName := hprim.Prim // identified primitive; e.g. "if", "if_else" m := hprim.Nodes // node mapping newName := hprim.Node // new node name // Create a control flow primitive based on the identified subgraph. primBBs := make(map[string]BasicBlock) for _, gname := range m { bb, ok := bbs[gname] if !ok { return nil, errutil.Newf("unable to locate basic block %q", gname) } primBBs[gname] = bb delete(bbs, gname) } prim, err := createPrim(subName, m, primBBs, newName) if err != nil { return nil, errutil.Err(err) } if flagVerbose && !flagQuiet { fmt.Println("located primitive:") printBB(prim) } bbs[prim.Name()] = prim } for _, bb := range bbs { if !bb.Term().IsNil() { // TODO: Remove debug output. bb.Term().Dump() return nil, errutil.Newf("invalid terminator instruction of last basic block in function; expected nil since return statements are already handled") } block := &ast.BlockStmt{ List: bb.Stmts(), } return block, nil } return nil, errutil.New("unable to locate basic block") }
// createPreLoopPrim creates a pre-test loop primitive based on the identified // subgraph, its node pair mapping and its basic blocks. The new control flow // primitive conceptually represents a basic block with the given name. // // Contents of "pre_loop.dot": // // digraph pre_loop { // cond [label="entry"] // body // exit [label="exit"] // cond->body [label="true"] // body->cond // cond->exit [label="false"] // } func createPreLoopPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) { // Locate graph nodes. nameCond, ok := m["cond"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "cond"`) } nameBody, ok := m["body"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "body"`) } nameExit, ok := m["exit"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "exit"`) } bbCond, ok := bbs[nameCond] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameCond) } bbBody, ok := bbs[nameBody] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameBody) } bbExit, ok := bbs[nameExit] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameExit) } // Locate and expand the condition. // // // from: // _2 := i < 10 // if _2 { // // // to: // if i < 10 { cond, _, _, err := getBrCond(bbCond.Term()) if err != nil { return nil, errutil.Err(err) } cond, err = expand(bbCond, cond) if err != nil { return nil, errutil.Err(err) } if len(bbCond.Stmts()) != 0 { // Produce the following primitive instead of a regular for loop if cond // contains statements. // // for { // cond_stmts // if !cond { // break // } // body_true // } // exit // Create if-statement. ifStmt := &ast.IfStmt{ Cond: &ast.UnaryExpr{Op: token.NOT, X: cond}, // negate condition Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, } // Create for-loop. body := append(bbCond.Stmts(), ifStmt) body = append(body, bbBody.Stmts()...) forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{List: body}, } // Create primitive. stmts := []ast.Stmt{forStmt} stmts = append(stmts, bbExit.Stmts()...) prim := &primitive{ name: newName, stmts: stmts, term: bbExit.Term(), } return prim, nil } // Create and return new primitive. // // for cond { // body_true // } // exit // Create for-loop. forStmt := &ast.ForStmt{ Cond: cond, Body: &ast.BlockStmt{List: bbBody.Stmts()}, } // Create primitive. stmts := []ast.Stmt{forStmt} stmts = append(stmts, bbExit.Stmts()...) prim := &primitive{ name: newName, stmts: stmts, term: bbExit.Term(), } return prim, nil }
// createIfElsePrim creates an if-else primitive based on the identified // subgraph, its node pair mapping and its basic blocks. The new control flow // primitive conceptually represents a basic block with the given name. // // Contents of "if_else.dot": // // digraph if_else { // cond [label="entry"] // body_true // body_false // exit [label="exit"] // cond->body_true [label="true"] // cond->body_false [label="false"] // body_true->exit // body_false->exit // } func createIfElsePrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) { // Locate graph nodes. nameCond, ok := m["cond"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "cond"`) } nameBodyTrue, ok := m["body_true"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "body_true"`) } nameBodyFalse, ok := m["body_false"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "body_false"`) } nameExit, ok := m["exit"] if !ok { return nil, errutil.New(`unable to locate node pair for sub node "exit"`) } bbCond, ok := bbs[nameCond] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameCond) } // The body nodes (body_true and body_false) of if-else primitives are // indistinguishable at the graph level. Verify their names against the // terminator instruction of the basic block and swap them if necessary. cond, targetTrue, targetFalse, err := getBrCond(bbCond.Term()) if err != nil { return nil, errutil.Err(err) } if targetTrue != nameBodyTrue && targetTrue != nameBodyFalse { return nil, errutil.Newf("invalid target true branch; got %q, expected %q or %q", targetTrue, nameBodyTrue, nameBodyFalse) } if targetFalse != nameBodyTrue && targetFalse != nameBodyFalse { return nil, errutil.Newf("invalid target false branch; got %q, expected %q or %q", targetFalse, nameBodyTrue, nameBodyFalse) } nameBodyTrue = targetTrue nameBodyFalse = targetFalse bbBodyTrue, ok := bbs[nameBodyTrue] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameBodyTrue) } bbBodyFalse, ok := bbs[nameBodyFalse] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameBodyFalse) } bbExit, ok := bbs[nameExit] if !ok { return nil, errutil.Newf("unable to locate basic block %q", nameExit) } // Create and return new primitive. // // cond_stmts // if cond { // body_true // } else { // body_false // } // exit // Create if-else statement. ifElseStmt := &ast.IfStmt{ Cond: cond, Body: &ast.BlockStmt{List: bbBodyTrue.Stmts()}, Else: &ast.BlockStmt{List: bbBodyFalse.Stmts()}, } // Create primitive. stmts := append(bbCond.Stmts(), ifElseStmt) stmts = append(stmts, bbExit.Stmts()...) prim := &primitive{ name: newName, stmts: stmts, term: bbExit.Term(), } return prim, nil }
// findPrim locates a control flow primitive in the provided control flow graph // and merges its nodes into a single node. func findPrim(graph *dot.Graph, subs []*graphs.SubGraph, step int) (*primitive.Primitive, error) { for _, sub := range subs { // Locate an isomorphism of sub in graph. m, ok := iso.Search(graph, sub) if !ok { // No match, try next control flow primitive. continue } if flagVerbose && !flagQuiet { printMapping(graph, sub, m) } // Output pre-merge intermediate control flow graphs. if flagSteps { // Highlight nodes to be replaced in red. for _, preNodeName := range m { preNode, ok := graph.Nodes.Lookup[preNodeName] if !ok { return nil, errutil.Newf("unable to locate pre-merge node %q", preNodeName) } if preNode.Attrs == nil { preNode.Attrs = dot.NewAttrs() } preNode.Attrs["fillcolor"] = "red" preNode.Attrs["style"] = "filled" } // Store pre-merge DOT graph. stepName := fmt.Sprintf("%s_%da", graph.Name, step) if err := createDOT(stepName, graph); err != nil { return nil, errutil.Err(err) } // Restore node colour. for _, preNodeName := range m { preNode, ok := graph.Nodes.Lookup[preNodeName] if !ok { return nil, errutil.Newf("unable to locate pre-merge node %q", preNodeName) } delete(preNode.Attrs, "fillcolor") delete(preNode.Attrs, "style") } } // Check if one of the nodes to be merged has the label "entry". hasEntry := false for _, preNodeName := range m { preNode, ok := graph.Nodes.Lookup[preNodeName] if !ok { return nil, errutil.Newf("unable to locate pre-merge node %q", preNodeName) } if preNode.Attrs != nil && preNode.Attrs["label"] == "entry" { hasEntry = true break } } // Merge the nodes of the subgraph isomorphism into a single node. postNodeName, err := merge.Merge(graph, m, sub) if err != nil { return nil, errutil.Err(err) } // Add "entry" label to new node if present in the pre-merge nodes. if hasEntry { postNode, ok := graph.Nodes.Lookup[postNodeName] if !ok { return nil, errutil.Newf("unable to locate post-merge node %q", postNodeName) } if postNode.Attrs == nil { postNode.Attrs = dot.NewAttrs() } postNode.Attrs["label"] = "entry" index := postNode.Index graph.Nodes.Nodes[0], graph.Nodes.Nodes[index] = postNode, graph.Nodes.Nodes[0] } // Output post-merge intermediate control flow graphs. if flagSteps { // Highlight node to be replaced in red. postNode, ok := graph.Nodes.Lookup[postNodeName] if !ok { return nil, errutil.Newf("unable to locate post-merge node %q", postNodeName) } if postNode.Attrs == nil { postNode.Attrs = dot.NewAttrs() } postNode.Attrs["fillcolor"] = "red" postNode.Attrs["style"] = "filled" // Store post-merge DOT graph. stepName := fmt.Sprintf("%s_%db", graph.Name, step) if err := createDOT(stepName, graph); err != nil { return nil, errutil.Err(err) } // Restore node colour. delete(postNode.Attrs, "fillcolor") delete(postNode.Attrs, "style") } // Create a new control flow primitive. prim := &primitive.Primitive{ Node: postNodeName, Prim: sub.Name, Nodes: m, } return prim, nil } return nil, errutil.New("unable to locate control flow primitive") }