// Package ircheck implements a checker for LLVM IR, that goes a bit further // than the regular LLVM IR verifier. Note that it checks different things, so // this is not a replacement for the LLVM verifier but does catch things that // the LLVM verifier doesn't catch. package ircheck import ( "errors" "fmt" "tinygo.org/x/go-llvm" ) type checker struct { ctx llvm.Context } func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // prevent infinite recursion for self-referential types if _, ok := checked[t]; ok { return nil } checked[t] = struct{}{} // check for any context mismatches switch { case t.Context() == c.ctx: // this is correct case t.Context() == llvm.GlobalContext(): // somewhere we accidentally used the global context instead of a real context return fmt.Errorf("type %q uses global context", t.String()) default: // we used some other context by accident return fmt.Errorf("type %q uses context %v instead of the main context %v", t.String(), t.Context(), c.ctx) } // if this is a composite type, check the components of the type switch t.TypeKind() { case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind: // there should only be one of any of these if s, ok := specials[t.TypeKind()]; !ok { specials[t.TypeKind()] = t } else if s != t { return fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s) } case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind: // floating point numbers are primitives - nothing to recurse case llvm.IntegerTypeKind: // integers are primitives - nothing to recurse case llvm.FunctionTypeKind: // check arguments and return(s) for i, v := range t.ParamTypes() { if err := c.checkType(v, checked, specials); err != nil { return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error()) } } if err := c.checkType(t.ReturnType(), checked, specials); err != nil { return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error()) } case llvm.StructTypeKind: // check all elements for i, v := range t.StructElementTypes() { if err := c.checkType(v, checked, specials); err != nil { return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error()) } } case llvm.ArrayTypeKind: // check element type if err := c.checkType(t.ElementType(), checked, specials); err != nil { return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error()) } case llvm.PointerTypeKind: // Pointers can't be checked in an opaque pointer world. case llvm.VectorTypeKind: // check element type if err := c.checkType(t.ElementType(), checked, specials); err != nil { return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error()) } default: return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String()) } return nil } func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // check type if err := c.checkType(v.Type(), types, specials); err != nil { return fmt.Errorf("failed to verify type of value: %s", err.Error()) } // check if this is an undefined void if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind { return errors.New("encountered undefined void value") } return nil } func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // check value properties if err := c.checkValue(inst, types, specials); err != nil { return errorAt(inst, err.Error()) } // The alloca instruction can be present in every basic block. However, // allocas in basic blocks other than the entry basic block have a number of // problems: // * They are hard to optimize, leading to potential missed optimizations. // * They may cause stack overflows in loops that would otherwise be // innocent. // * They cause extra code to be generated, because it requires the use of // a frame pointer. // * Perhaps most importantly, the coroutine lowering pass of LLVM (as of // LLVM 9) cannot deal with these allocas: // https://llvm.org/docs/Coroutines.html // Therefore, alloca instructions should be limited to the entry block. if !inst.IsAAllocaInst().IsNil() { if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() { return errorAt(inst, "internal error: non-static alloca") } } // check operands for i := 0; i < inst.OperandsCount(); i++ { if err := c.checkValue(inst.Operand(i), types, specials); err != nil { return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error())) } } return nil } func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { // check basic block value and type var errs []error if err := c.checkValue(bb.AsValue(), types, specials); err != nil { errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err))) } // check instructions for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if err := c.checkInstruction(inst, types, specials); err != nil { errs = append(errs, err) } } return errs } func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { // check function value and type var errs []error if err := c.checkValue(fn, types, specials); err != nil { errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())) } // check basic blocks for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { errs = append(errs, c.checkBasicBlock(bb, types, specials)...) } return errs } // Module checks the given module and returns a slice of error, if there are // any. func Module(mod llvm.Module) []error { // check for any context mismatches var errs []error c := checker{ ctx: mod.Context(), } if c.ctx == llvm.GlobalContext() { // somewhere we accidentally used the global context instead of a real context errs = append(errs, errors.New("module uses global context")) } types := map[llvm.Type]struct{}{} specials := map[llvm.TypeKind]llvm.Type{} for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { errs = append(errs, c.checkFunction(fn, types, specials)...) } for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) { if err := c.checkValue(g, types, specials); err != nil { errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())) } } return errs }