// endBody finalizes the generation of the function body. func (f *Function) endBody() error { if block := f.curBlock; block != nil && block.Term() == nil { switch { case f.Function.Name() == "main": // From C11 spec $5.1.2.2.3. // // "If the return type of the main function is a type compatible with // int, a return from the initial call to the main function is // equivalent to calling the exit function with the value returned by // the main function as its argument; reaching the } that terminates // the main function returns a value of 0." result := f.Sig().Result() zero := constZero(result) term, err := instruction.NewRet(zero) if err != nil { panic(fmt.Sprintf("unable to create ret terminator; %v", err)) } block.SetTerm(term) default: // Add void return terminator to the current basic block, if a // terminator is missing. switch result := f.Sig().Result(); { case irtypes.IsVoid(result): term, err := instruction.NewRet(nil) if err != nil { panic(fmt.Sprintf("unable to create ret instruction; %v", err)) } block.SetTerm(term) default: // The semantic analysis checker guarantees that all branches of // non-void functions end with return statements. Therefore, if we // reach the current basic block doesn't have a terminator at the // end of the function body, it must be unreachable. term, err := instruction.NewUnreachable() if err != nil { panic(fmt.Sprintf("unable to create unreachable instruction; %v", err)) } block.SetTerm(term) } } } f.curBlock = nil if err := f.AssignIDs(); err != nil { return errutil.Err(err) } return nil }
// assignIDs assigns unique IDs to unnamed basic blocks and local variable // definitions. func (block *BasicBlock) assignIDs() error { f := block.Parent() // Named represents a named basic block or local variable definition. type Named interface { Name() string SetName(name string) } // setName assigns unique local IDs to unnamed basic blocks and local // variable definitions. setName := func(n Named) error { // TODO: Ensure that global variables cannot be mixed up with local // variables. This should be easy, as global variables may not be unnamed. // Check that global variables are always given a name during creation. if name := n.Name(); len(name) == 0 { n.SetName(f.nextID()) } else if isLocalID(name) { // Validate that explicitly named local IDs conform to the localID // counter and update the localID counter to keep explicitly and // implicitly named local IDs in sync. if want := f.nextID(); name != want { return errutil.Newf("invalid local ID; expected %s, got %s", enc.Local(want), enc.Local(name)) } } return nil } // Assign unique local IDs to unnamed basic blocks. if err := setName(block); err != nil { return errutil.Err(err) } // Assign unique local IDs to unnamed local variable definitions. for _, inst := range block.Insts() { if def, ok := inst.(*instruction.LocalVarDef); ok { if !types.IsVoid(def.ValInst().RetType()) { if err := setName(def); err != nil { return errutil.Err(err) } } } } return nil }
// NewRet returns a new ret instruction based on the given return value. A nil // return value indicates a "void" return instruction. func NewRet(val value.Value) (*Ret, error) { if val != nil && types.IsVoid(val.Type()) { return nil, errutil.Newf(`expected no return value for return type "void"; got %q`, val) } return &Ret{val: val}, nil }
// String returns the string representation of the local variable definition; // e.g. // // %foo = add i32 13, 42 func (def *LocalVarDef) String() string { if types.IsVoid(def.Type()) || len(def.Name()) == 0 { return def.valInst.String() } return fmt.Sprintf("%s = %s", def.ValueString(), def.valInst) }