// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main // Stub package for testing ssa compiler backend. Will eventually // be deleted when ssa is called directly from the main compiler. import ( "bufio" "flag" "fmt" "io" "os" "strconv" "strings" "cmd/internal/ssa/types" "cmd/internal/ssa" ) // testing harness which runs the compiler using an IR read from a file func main() { flag.Parse() file := flag.Arg(0) r, err := os.Open(file) if err != nil { panic(err) } f := buildFunc(readFunc(r)) ssa.Compile(f) // TODO: output f } // readFunc reads the intermediate representation generated by the // compiler frontend and returns it as a list of sexpressions. func readFunc(r io.Reader) []sexpr { var lines []sexpr s := bufio.NewScanner(r) for s.Scan() { line := s.Text() e := parseSexpr(strings.Trim(line, " ")) if !e.compound { panic("bad stmt: " + line) } if e.parts[0].compound { panic("bad op: " + line) } lines = append(lines, e) } return lines } // buildFunc converts from the 6g IR dump format to the internal // form. Builds SSA and all that. func buildFunc(lines []sexpr) *ssa.Func { f := new(ssa.Func) // We construct SSA using an algorithm similar to // Brau, Buchwald, Hack, Leißa, Mallon, and Zwinkau // http://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf // allocate starting block f.Entry = f.NewBlock(ssa.BlockPlain) // TODO: all args. Make a struct containing args/returnvals, declare // an FP which contains a pointer to that struct. var exit *ssa.Block // all returns (if any) branch to here TODO: defers & panics? // add a block for each label // Also a few other preprocessing steps, all in one pass. labels := map[string]*ssa.Block{} types := map[string]ssa.Type{} callFallthrough := map[int]*ssa.Block{} for i, e := range lines { switch e.parts[0].name { case "LABEL": labels[e.parts[1].name] = f.NewBlock(ssa.BlockPlain) case "NAME": f.Name = e.parts[1].name case "RETURN": if exit == nil { exit = f.NewBlock(ssa.BlockExit) } case "TYPE": types[e.parts[1].name] = parseSexprType(e.parts[2]) case "CALL": // allocate a new block for fallthrough callFallthrough[i] = f.NewBlock(ssa.BlockPlain) if exit == nil { exit = f.NewBlock(ssa.BlockExit) } } } // map from block id to sexprs in that block blocklines := make([][]sexpr, f.NumBlocks()) // Add sexprs to the correct block. Add edges between blocks. b := f.Entry var i int for j, e := range lines { if b == nil && e.parts[0].name != "LABEL" { // dead code (e.g. return in "if" branch makes the "goto end" statement dead) continue } switch e.parts[0].name { case "IF": if b.Kind != ssa.BlockPlain { panic("bad b state") } b.Kind = ssa.BlockIf edge(b, labels[e.parts[2].name]) edge(b, labels[e.parts[3].name]) blocklines[b.ID] = lines[i : j+1] b = nil case "GOTO": edge(b, labels[e.parts[1].name]) blocklines[b.ID] = lines[i:j] b = nil case "LABEL": b = labels[e.parts[1].name] i = j + 1 case "RETURN": if b.Kind != ssa.BlockPlain { panic("bad b state") } edge(b, exit) blocklines[b.ID] = lines[i:j] b = nil case "CALL": if b.Kind != ssa.BlockPlain { panic("bad b state") } b.Kind = ssa.BlockCall c := callFallthrough[j] edge(b, c) edge(b, exit) blocklines[b.ID] = lines[i : j+1] b = c i = j + 1 } // note that we don't keep goto/label/return sexprs } if b != nil { panic("control flow falls off end of function") } // Read types for each variable // Number the variables densely varids := map[string]int{} // map from variable name to id var varnames []string // map from id to variable name var vartypes []ssa.Type // map from variable id to type for _, e := range lines { if e.parts[0].name != "DCL" { continue } name := e.parts[1].name if _, ok := varids[name]; ok { continue } id := len(varids) if id == 1<<31-1 { panic("too many variables") } fmt.Printf("var %d = %s\n", id, name) varids[name] = id varnames = append(varnames, name) vartypes = append(vartypes, types[e.parts[2].name]) } memID := len(varids) fmt.Printf("var %d = .mem\n", memID) varids[".mem"] = memID // TODO: need .mem here? varnames = append(varnames, ".mem") vartypes = append(vartypes, ssa.TypeMem) // map from variable ID to current Value of that variable curBlock := NewSparseMap(len(varids)) var state ssaFuncState state.types = types state.varids = varids state.varnames = varnames state.vartypes = vartypes state.curBlock = curBlock state.done = make([]bool, f.NumBlocks()) state.defs = map[blockvar]*ssa.Value{} state.memID = memID // Convert each block to ssa // TODO: order blocks for maximum happiness - we want to process // all the predecessors of a block before processing the block itself, // if at all possible. for _, b := range f.Blocks { fmt.Printf("processing block %d\n", b.ID) curBlock.Clear() for _, e := range blocklines[b.ID] { switch e.parts[0].name { case "AS": if e.parts[1].compound { // store expression lhs := genExpr(&state, b, e.parts[1]) rhs := genExpr(&state, b, e.parts[2]) mem := genVar(&state, b, memID) v := b.NewValue(ssa.OpStore, ssa.TypeMem, nil) v.AddArg(lhs) v.AddArg(rhs) v.AddArg(mem) curBlock.Put(memID, v) } else { // variable assignment v := genExpr(&state, b, e.parts[2]) curBlock.Put(varids[e.parts[1].name], v) } case "DCL": // nothing to do case "IF": b.Control = genExpr(&state, b, e.parts[1]) case "CALL": // only direct call for now - indirect call takes addr value as well v := b.NewValue(ssa.OpStaticCall, ssa.TypeMem, e.parts[1].name) v.AddArg(genVar(&state, b, memID)) curBlock.Put(memID, v) b.Control = v } } // link up thunks to their actual values for _, v := range b.Values { if v.Op != ssa.OpThunk { continue } varid := v.Aux.(int) w := genVar(&state, b, varid) v.Op = ssa.OpCopy v.Aux = nil v.AddArg(w) } // record final values at the end of the block for _, e := range curBlock.Contents() { state.defs[blockvar{b.ID, e.Key}] = e.Val // TODO: somehow avoid storing dead values to this map. } curBlock.Clear() state.done[b.ID] = true } // the final store value is returned if exit != nil { exit.Control = genVar(&state, exit, memID) } return f } func edge(a, b *ssa.Block) { a.Succs = append(a.Succs, b) b.Preds = append(b.Preds, a) } func genVar(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value { // look up variable v := state.curBlock.Get(id) if v != nil { // variable was defined previously in this block // (or we memoized the result) return v } // Variable comes in from outside of basic block. v = lookupVarIncoming(state, b, id) // memoize result so future callers will not look it up again state.curBlock.Put(id, v) return v } func genExpr(state *ssaFuncState, b *ssa.Block, e sexpr) *ssa.Value { if !e.compound { return genVar(state, b, state.varids[e.name]) } switch e.parts[0].name { case "ADD": x := genExpr(state, b, e.parts[1]) y := genExpr(state, b, e.parts[2]) v := b.NewValue(ssa.OpAdd, x.Type, nil) v.AddArg(x) v.AddArg(y) return v case "SUB": x := genExpr(state, b, e.parts[1]) y := genExpr(state, b, e.parts[2]) v := b.NewValue(ssa.OpSub, x.Type, nil) v.AddArg(x) v.AddArg(y) return v case "CINT": c, err := strconv.ParseInt(e.parts[1].name, 10, 64) if err != nil { panic("bad cint value") } return b.Func.ConstInt(c) case "LT": x := genExpr(state, b, e.parts[1]) y := genExpr(state, b, e.parts[2]) v := b.NewValue(ssa.OpLess, ssa.TypeBool, nil) v.AddArg(x) v.AddArg(y) return v case "FP": typ := state.types[e.parts[1].name] offset, err := strconv.ParseInt(e.parts[2].name, 10, 64) if err != nil { panic(err) } v := b.NewValue(ssa.OpFPAddr, types.NewPointer(typ), offset) return v case "SP": typ := state.types[e.parts[1].name] offset, err := strconv.ParseInt(e.parts[2].name, 10, 64) if err != nil { panic(err) } v := b.NewValue(ssa.OpSPAddr, types.NewPointer(typ), offset) return v case "LOAD": p := genExpr(state, b, e.parts[1]) v := b.NewValue(ssa.OpLoad, p.Type.(*types.Pointer).Elem(), nil) v.AddArg(p) v.AddArg(genVar(state, b, state.memID)) return v default: fmt.Println(e.parts[0].name) panic("unknown op") } } // map key combining block id and variable id type blockvar struct { bid ssa.ID varid int } type ssaFuncState struct { types map[string]ssa.Type varnames []string varids map[string]int vartypes []ssa.Type curBlock *SparseMap // value of each variable in block we're working on defs map[blockvar]*ssa.Value // values for variables at the end of blocks done []bool memID int } // Find the value of the variable with the given id leaving block b. func lookupVarOutgoing(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value { fmt.Printf("lookupOutgoing var=%d block=%d\n", id, b.ID) v := state.defs[blockvar{b.ID, id}] if v != nil { return v } if state.done[b.ID] { // The variable was not defined in this block, and we haven't // memoized the answer yet. Look it up recursively. This might // cause infinite recursion, so add a copy first. v = b.NewValue(ssa.OpCopy, state.vartypes[id], nil) state.defs[blockvar{b.ID, id}] = v v.AddArg(lookupVarIncoming(state, b, id)) return v } // We don't know about defined variables in this block (yet). // Make a thunk for this variable. fmt.Printf("making thunk for var=%d in block=%d\n", id, b.ID) v = b.NewValue(ssa.OpThunk, state.vartypes[id], id) // memoize result state.defs[blockvar{b.ID, id}] = v return v } // Find the Value of the variable coming into block b. func lookupVarIncoming(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value { fmt.Printf("lookupIncoming var=%d block=%d\n", id, b.ID) var v *ssa.Value switch len(b.Preds) { case 0: // TODO: handle function args some other way (assignments in starting block?) // TODO: error if variable isn't a function arg (including mem input) v = b.NewValue(ssa.OpArg, state.vartypes[id], state.varnames[id]) case 1: v = lookupVarOutgoing(state, b.Preds[0], id) default: v = b.NewValue(ssa.OpCopy, state.vartypes[id], nil) args := make([]*ssa.Value, len(b.Preds)) for i, p := range b.Preds { args[i] = lookupVarOutgoing(state, p, id) } // if <=1 value that isn't this variable's thunk, don't make phi v.Op = ssa.OpPhi v.AddArgs(args...) // note: order corresponding to b.Pred } return v } func parseSexprType(e sexpr) ssa.Type { if !e.compound { switch e.name { case "int": return ssa.TypeInt default: fmt.Println(e.name) panic("unknown type") } } if e.parts[0].name == "FUNC" { // TODO: receiver? Already folded into args? Variadic? var args, rets []*types.Var for _, s := range e.parts[1].parts { t := parseSexprType(s) args = append(args, types.NewParam(0, nil, "noname", t)) } for _, s := range e.parts[2].parts { t := parseSexprType(s) rets = append(rets, types.NewParam(0, nil, "noname", t)) } sig := types.NewSignature(nil, nil, types.NewTuple(args...), types.NewTuple(rets...), false) return ssa.Type(sig) } // TODO: array/struct/... panic("compound type") }