github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compiler/ircheck/check.go (about) 1 // Package ircheck implements a checker for LLVM IR, that goes a bit further 2 // than the regular LLVM IR verifier. Note that it checks different things, so 3 // this is not a replacement for the LLVM verifier but does catch things that 4 // the LLVM verifier doesn't catch. 5 package ircheck 6 7 import ( 8 "errors" 9 "fmt" 10 11 "tinygo.org/x/go-llvm" 12 ) 13 14 type checker struct { 15 ctx llvm.Context 16 } 17 18 func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { 19 // prevent infinite recursion for self-referential types 20 if _, ok := checked[t]; ok { 21 return nil 22 } 23 checked[t] = struct{}{} 24 25 // check for any context mismatches 26 switch { 27 case t.Context() == c.ctx: 28 // this is correct 29 case t.Context() == llvm.GlobalContext(): 30 // somewhere we accidentally used the global context instead of a real context 31 return fmt.Errorf("type %q uses global context", t.String()) 32 default: 33 // we used some other context by accident 34 return fmt.Errorf("type %q uses context %v instead of the main context %v", t.String(), t.Context(), c.ctx) 35 } 36 37 // if this is a composite type, check the components of the type 38 switch t.TypeKind() { 39 case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind: 40 // there should only be one of any of these 41 if s, ok := specials[t.TypeKind()]; !ok { 42 specials[t.TypeKind()] = t 43 } else if s != t { 44 return fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s) 45 } 46 case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind: 47 // floating point numbers are primitives - nothing to recurse 48 case llvm.IntegerTypeKind: 49 // integers are primitives - nothing to recurse 50 case llvm.FunctionTypeKind: 51 // check arguments and return(s) 52 for i, v := range t.ParamTypes() { 53 if err := c.checkType(v, checked, specials); err != nil { 54 return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error()) 55 } 56 } 57 if err := c.checkType(t.ReturnType(), checked, specials); err != nil { 58 return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error()) 59 } 60 case llvm.StructTypeKind: 61 // check all elements 62 for i, v := range t.StructElementTypes() { 63 if err := c.checkType(v, checked, specials); err != nil { 64 return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error()) 65 } 66 } 67 case llvm.ArrayTypeKind: 68 // check element type 69 if err := c.checkType(t.ElementType(), checked, specials); err != nil { 70 return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error()) 71 } 72 case llvm.PointerTypeKind: 73 // Pointers can't be checked in an opaque pointer world. 74 case llvm.VectorTypeKind: 75 // check element type 76 if err := c.checkType(t.ElementType(), checked, specials); err != nil { 77 return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error()) 78 } 79 default: 80 return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String()) 81 } 82 83 return nil 84 } 85 86 func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { 87 // check type 88 if err := c.checkType(v.Type(), types, specials); err != nil { 89 return fmt.Errorf("failed to verify type of value: %s", err.Error()) 90 } 91 92 // check if this is an undefined void 93 if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind { 94 return errors.New("encountered undefined void value") 95 } 96 97 return nil 98 } 99 100 func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { 101 // check value properties 102 if err := c.checkValue(inst, types, specials); err != nil { 103 return errorAt(inst, err.Error()) 104 } 105 106 // The alloca instruction can be present in every basic block. However, 107 // allocas in basic blocks other than the entry basic block have a number of 108 // problems: 109 // * They are hard to optimize, leading to potential missed optimizations. 110 // * They may cause stack overflows in loops that would otherwise be 111 // innocent. 112 // * They cause extra code to be generated, because it requires the use of 113 // a frame pointer. 114 // * Perhaps most importantly, the coroutine lowering pass of LLVM (as of 115 // LLVM 9) cannot deal with these allocas: 116 // https://llvm.org/docs/Coroutines.html 117 // Therefore, alloca instructions should be limited to the entry block. 118 if !inst.IsAAllocaInst().IsNil() { 119 if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() { 120 return errorAt(inst, "internal error: non-static alloca") 121 } 122 } 123 124 // check operands 125 for i := 0; i < inst.OperandsCount(); i++ { 126 if err := c.checkValue(inst.Operand(i), types, specials); err != nil { 127 return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error())) 128 } 129 } 130 131 return nil 132 } 133 134 func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { 135 // check basic block value and type 136 var errs []error 137 if err := c.checkValue(bb.AsValue(), types, specials); err != nil { 138 errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err))) 139 } 140 141 // check instructions 142 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 143 if err := c.checkInstruction(inst, types, specials); err != nil { 144 errs = append(errs, err) 145 } 146 } 147 148 return errs 149 } 150 151 func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { 152 // check function value and type 153 var errs []error 154 if err := c.checkValue(fn, types, specials); err != nil { 155 errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())) 156 } 157 158 // check basic blocks 159 for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { 160 errs = append(errs, c.checkBasicBlock(bb, types, specials)...) 161 } 162 163 return errs 164 } 165 166 // Module checks the given module and returns a slice of error, if there are 167 // any. 168 func Module(mod llvm.Module) []error { 169 // check for any context mismatches 170 var errs []error 171 c := checker{ 172 ctx: mod.Context(), 173 } 174 if c.ctx == llvm.GlobalContext() { 175 // somewhere we accidentally used the global context instead of a real context 176 errs = append(errs, errors.New("module uses global context")) 177 } 178 179 types := map[llvm.Type]struct{}{} 180 specials := map[llvm.TypeKind]llvm.Type{} 181 for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 182 errs = append(errs, c.checkFunction(fn, types, specials)...) 183 } 184 for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) { 185 if err := c.checkValue(g, types, specials); err != nil { 186 errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())) 187 } 188 } 189 190 return errs 191 }