diff --git a/lib/InitModelTypes/InitModelTypes.cpp b/lib/InitModelTypes/InitModelTypes.cpp index f0af7843b..25049d9e9 100644 --- a/lib/InitModelTypes/InitModelTypes.cpp +++ b/lib/InitModelTypes/InitModelTypes.cpp @@ -2,17 +2,23 @@ // Copyright rev.ng Labs Srl. See LICENSE.md for details. // +#include +#include + #include "llvm/ADT/Optional.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Type.h" #include "llvm/IR/Value.h" #include "llvm/Support/Casting.h" #include "revng/ABI/FunctionType/Layout.h" +#include "revng/ADT/RecursiveCoroutine.h" #include "revng/EarlyFunctionAnalysis/FunctionMetadataCache.h" #include "revng/Model/Architecture.h" #include "revng/Model/Binary.h" @@ -315,11 +321,393 @@ static void handleCallInstruction(FunctionMetadataCache &Cache, } } -ModelTypesMap initModelTypes(FunctionMetadataCache &Cache, - const llvm::Function &F, - const model::Function *ModelF, - const Binary &Model, - bool PointersOnly) { +static model::PrimitiveTypeKind::Values +getPrimitiveKind(const model::QualifiedType &QT) { + revng_assert(QT.isPrimitive()); + model::QualifiedType Unwrapped = peelConstAndTypedefs(QT); + revng_assert(Unwrapped.Qualifiers().empty()); + auto *Primitive = llvm::cast(Unwrapped.UnqualifiedType() + .getConst()); + return Primitive->PrimitiveKind(); +} + +static model::PrimitiveTypeKind::Values +getCommonPrimitiveKind(model::PrimitiveTypeKind::Values A, + model::PrimitiveTypeKind::Values B) { + if (A == B) + return A; + + if (A == model::PrimitiveTypeKind::Generic + or B == model::PrimitiveTypeKind::Generic) + return model::PrimitiveTypeKind::Generic; + + // Here, neither A nor B are Generic + + // Given that A != B, and they're not generic, if either of them is Float, we + // directly go to Generic. + if (A == model::PrimitiveTypeKind::Float + or B == model::PrimitiveTypeKind::Float) + return model::PrimitiveTypeKind::Generic; + + // Here neither A nor B is Generic nor Float + + // If either is PointerOrNumber, we go to PointerOrNumber. + if (A == model::PrimitiveTypeKind::PointerOrNumber + or B == model::PrimitiveTypeKind::PointerOrNumber) + return model::PrimitiveTypeKind::PointerOrNumber; + + // Here neither A nor B is Generic, Float, nor PointerOrNumber + // Here A and B can only be Number, Signed or Unsigned. + // Given that they are different, we always go to Number. + return model::PrimitiveTypeKind::Number; +} + +static model::QualifiedType +getEnumUnderlyingType(const model::QualifiedType &QT) { + revng_assert(QT.is(model::TypeKind::EnumType)); + model::QualifiedType Unwrapped = peelConstAndTypedefs(QT); + revng_assert(Unwrapped.Qualifiers().empty()); + auto *Enum = llvm::cast(Unwrapped.UnqualifiedType() + .getConst()); + return Enum->UnderlyingType(); +} + +static std::optional +getCommonScalarType(const model::QualifiedType &A, + const model::QualifiedType &B, + const model::Binary &Model) { + + using model::PrimitiveTypeKind::Values::Float; + using model::PrimitiveTypeKind::Values::Generic; + using model::PrimitiveTypeKind::Values::PointerOrNumber; + + revng_assert(A.isScalar()); + revng_assert(B.isScalar()); + + if (A == B) + return A; + + revng_assert(A.isPrimitive() or A.isPointer() + or A.is(model::TypeKind::EnumType)); + revng_assert(B.isPrimitive() or B.isPointer() + or B.is(model::TypeKind::EnumType)); + + revng_assert(A.size() == B.size()); + uint64_t Size = A.size().value(); + + if (A.isPrimitive() and B.isPrimitive()) { + model::PrimitiveTypeKind::Values AKind = getPrimitiveKind(A); + model::PrimitiveTypeKind::Values BKind = getPrimitiveKind(B); + model::PrimitiveTypeKind::Values CommonKind = getCommonPrimitiveKind(AKind, + BKind); + return model::QualifiedType(Model.getPrimitiveType(CommonKind, Size), {}); + } + + if (A.isPrimitive() or B.isPrimitive()) { + + const model::QualifiedType &Primitive = A.isPrimitive() ? A : B; + model::PrimitiveTypeKind::Values + PrimitiveKind = getPrimitiveKind(Primitive); + + const model::QualifiedType &Other = A.isPrimitive() ? B : A; + + if (Other.isPointer()) { + + if (PrimitiveKind == Generic) + return Other; + + if (PrimitiveKind == Float) + return model::QualifiedType(Model.getPrimitiveType(Generic, Size), {}); + + return model::QualifiedType(Model.getPrimitiveType(PointerOrNumber, Size), + {}); + + } else if (Other.is(model::TypeKind::EnumType)) { + + model::PrimitiveTypeKind::Values + OtherKind = getPrimitiveKind(getEnumUnderlyingType(Other)); + + model::PrimitiveTypeKind::Values + CommonKind = getCommonPrimitiveKind(PrimitiveKind, OtherKind); + + return model::QualifiedType(Model.getPrimitiveType(CommonKind, Size), {}); + + } else { + revng_abort(); + } + } + + // Here neither A nor B are primitive. They are either enums or pointers. + // If one is a pointer and the other is an enum, we can't find a common type. + if (A.isPointer() and B.is(model::TypeKind::EnumType)) + return std::nullopt; + if (B.isPointer() and A.is(model::TypeKind::EnumType)) + return std::nullopt; + + if (A.is(model::TypeKind::EnumType) and B.is(model::TypeKind::EnumType)) { + // Make the common integer among the underlying types + model::PrimitiveTypeKind::Values + AKind = getPrimitiveKind(getEnumUnderlyingType(A)); + model::PrimitiveTypeKind::Values + BKind = getPrimitiveKind(getEnumUnderlyingType(B)); + model::PrimitiveTypeKind::Values CommonKind = getCommonPrimitiveKind(AKind, + BKind); + return model::QualifiedType(Model.getPrimitiveType(CommonKind, Size), {}); + } + + if (A.isPointer() and B.isPointer()) { + // Make a pointerornumber of the proper size (or could we do a void *) + return model::QualifiedType(Model.getPrimitiveType(PointerOrNumber, Size), + {}); + } + + // This should be unreachable, but we return a nullopt, to fail gracefully + return std::nullopt; +} + +static llvm::SmallPtrSet +getTransitivePHIIncomings(const llvm::PHINode *PHI) { + llvm::SmallPtrSet NonPHIIncomings; + + llvm::SmallPtrSet VisitedPHIs = { PHI }; + llvm::SmallVector WorkList = { PHI }; + + do { + const llvm::PHINode *Current = WorkList.back(); + WorkList.pop_back(); + for (const llvm::Value *Incoming : Current->incoming_values()) { + if (const auto *IncomingPHI = dyn_cast(Incoming)) { + if (bool New = VisitedPHIs.insert(IncomingPHI).second) + WorkList.push_back(IncomingPHI); + } else { + NonPHIIncomings.insert(Incoming); + } + } + } while (not WorkList.empty()); + + return NonPHIIncomings; +} + +static RecursiveCoroutine> +initModelTypesImpl(FunctionMetadataCache &Cache, + const llvm::Instruction &I, + const llvm::Function &F, + const model::Function *ModelF, + const Binary &Model, + bool PointersOnly, + ModelTypesMap &TypeMap, + llvm::SmallPtrSet + VisitedPHIs = {}) { + + const auto *InstType = I.getType(); + + // Ignore operands of some custom opcodes + if (not isCallTo(&I, "revng_call_stack_arguments")) { + // Visit operands, in case they are constants, globals or constexprs + for (const llvm::Use &Op : I.operands()) { + + if (auto *Call = getCallToIsolatedFunction(&I); + Call and Call->isCallee(&Op)) { + // Isolated functions have their prototype in the model + // + // If it's a direct call to an isolated function we know the type of + // the function, which affects the type of the + auto *Called = Call->getCalledOperand(); + if (auto *CalledFunction = dyn_cast(Called)) { + auto Prototype = Cache.getCallSitePrototype(Model, Call); + revng_assert(Prototype.isValid() and not Prototype.empty()); + TypeMap.insert({ CalledFunction, createPointerTo(Prototype, Model) }); + continue; + } + } + addOperandType(Op, Model, TypeMap, PointersOnly); + } + } + + // Insert void types for consistency + if (InstType->isVoidTy()) { + using model::PrimitiveTypeKind::Values::Void; + QualifiedType VoidTy(Model.getPrimitiveType(Void, 0), {}); + TypeMap.insert({ &I, VoidTy }); + rc_return VoidTy; + } + // Function calls in the IR might correspond to real function calls in + // the binary or to special intrinsics used by the backend, so they need + // to be handled separately + if (auto *Call = dyn_cast(&I)) { + handleCallInstruction(Cache, Call, ModelF, Model, TypeMap, PointersOnly); + auto CallTypeIt = TypeMap.find(Call); + std::optional CallType = std::nullopt; + if (CallTypeIt != TypeMap.end()) + CallType = CallTypeIt->second; + rc_return CallType; + } + + // Only Call instructions can return aggregates + revng_assert(not InstType->isAggregateType()); + + // All ExtractValues should have been converted to OpaqueExtractValue + revng_assert(not isa(&I)); + + std::optional Type = std::nullopt; + + switch (I.getOpcode()) { + + case Instruction::Load: { + auto *Load = dyn_cast(&I); + + auto It = TypeMap.find(Load->getPointerOperand()); + if (It == TypeMap.end()) + rc_return std::nullopt; + + const auto &PtrOperandType = It->second; + + // If the pointer operand is a pointer in the model, we can exploit + // this information to assign a model type to the loaded value. Note + // that this makes sense only if the pointee is itself a pointer or a + // scalar value: if we find a load of N bits from a struct pointer, we + // don't know if we are loading the entire struct or only some of its + // fields. + // TODO: inspect the model to understand if we are loading the first + // field. + if (PtrOperandType.isPointer()) { + model::QualifiedType Pointee = dropPointer(PtrOperandType); + + if (areMemOpCompatible(Pointee, *Load->getType(), Model)) + Type = Pointee; + } + + // If it's not a pointer or a scalar of the right size, just + // fallback to the LLVM type + + } break; + + case Instruction::Alloca: { + // TODO: eventually AllocaInst will be replaced by calls to + // revng_local_variable with a type annotation + llvm::Type *BaseType = cast(&I)->getAllocatedType(); + revng_assert(BaseType->isSingleValueType()); + const model::Architecture::Values &Architecture = Model.Architecture(); + Type = llvmIntToModelType(BaseType, Model).getPointerTo(Architecture); + } break; + + case Instruction::Select: { + auto *Select = dyn_cast(&I); + const auto &Op1Entry = TypeMap.find(Select->getOperand(1)); + const auto &Op2Entry = TypeMap.find(Select->getOperand(2)); + + // If the two selected values have the same type, assign that type to + // the result + if (Op1Entry != TypeMap.end() and Op2Entry != TypeMap.end() + and Op1Entry->second == Op2Entry->second) + Type = Op1Entry->second; + + } break; + + // Handle zext from i1 to i8 + case Instruction::ZExt: { + auto *ZExt = dyn_cast(&I); + + auto IsBoolZext = ZExt->getSrcTy()->getScalarSizeInBits() == 1 + and ZExt->getDestTy()->getScalarSizeInBits() == 8; + + if (not PointersOnly and IsBoolZext) { + const llvm::Value *Operand = I.getOperand(0); + + // Forward the type if there is one + auto It = TypeMap.find(Operand); + if (It != TypeMap.end()) + Type = It->second; + } + + } break; + + // Handle trunc from i8 to i1 + case Instruction::Trunc: { + auto *Trunc = dyn_cast(&I); + + auto IsBoolTrunc = Trunc->getSrcTy()->getScalarSizeInBits() == 8 + and Trunc->getDestTy()->getScalarSizeInBits() == 1; + + if (not PointersOnly and IsBoolTrunc) { + const llvm::Value *Operand = I.getOperand(0); + + // Forward the type if there is one + auto It = TypeMap.find(Operand); + if (It != TypeMap.end()) + Type = It->second; + } + + } break; + + case Instruction::BitCast: + case Instruction::Freeze: + case Instruction::IntToPtr: + case Instruction::PtrToInt: { + // Forward the type if there is one + auto It = TypeMap.find(I.getOperand(0)); + if (It != TypeMap.end()) + Type = It->second; + } break; + + case Instruction::PHI: { + auto *PHI = cast(&I); + bool New = VisitedPHIs.insert(PHI).second; + if (New) { + llvm::SmallPtrSet + NonPHIIncomings = getTransitivePHIIncomings(PHI); + + for (const llvm::Value *Incoming : NonPHIIncomings) { + std::optional IncomingType = std::nullopt; + auto IncomingTypeIt = TypeMap.find(Incoming); + if (IncomingTypeIt != TypeMap.end()) { + IncomingType = IncomingTypeIt->second; + } else { + if (auto + *IncomingInstruction = dyn_cast(Incoming)) { + IncomingType = rc_recur initModelTypesImpl(Cache, + *IncomingInstruction, + F, + ModelF, + Model, + PointersOnly, + TypeMap, + VisitedPHIs); + } + } + if (not IncomingType) + continue; + + if (not Type) { + Type = IncomingType; + } else { + std::optional + CommonType = getCommonScalarType(*Type, *IncomingType, Model); + if (CommonType.has_value()) + Type = CommonType.value(); + else + Type = llvmIntToModelType(PHI->getType(), Model); + } + } + } + } break; + + default: + break; + } + + rc_return Type; +} + +static RecursiveCoroutine +initModelTypesImpl(FunctionMetadataCache &Cache, + const llvm::Function &F, + const model::Function *ModelF, + const Binary &Model, + bool PointersOnly, + llvm::SmallPtrSet + VisitedPHIs = {}) { + ModelTypesMap TypeMap; const model::Type *Prototype = ModelF->prototype(Model).getConst(); @@ -329,165 +717,14 @@ ModelTypesMap initModelTypes(FunctionMetadataCache &Cache, for (const BasicBlock *BB : RPOT(&F)) { for (const Instruction &I : *BB) { - - const auto *InstType = I.getType(); - - // Ignore operands of some custom opcodes - if (not isCallTo(&I, "revng_call_stack_arguments")) { - // Visit operands, in case they are constants, globals or constexprs - for (const llvm::Use &Op : I.operands()) { - - if (auto *Call = getCallToIsolatedFunction(&I); - Call and Call->isCallee(&Op)) { - // Isolated functions have their prototype in the model - // - // If it's a direct call to an isolated function we know the type of - // the function, which affects the type of the - auto *Called = Call->getCalledOperand(); - if (auto *CalledFunction = dyn_cast(Called)) { - auto Prototype = Cache.getCallSitePrototype(Model, Call); - revng_assert(Prototype.isValid() and not Prototype.empty()); - TypeMap.insert({ CalledFunction, - createPointerTo(Prototype, Model) }); - continue; - } - } - - addOperandType(Op, Model, TypeMap, PointersOnly); - } - } - - // Insert void types for consistency - if (InstType->isVoidTy()) { - using model::PrimitiveTypeKind::Values::Void; - QualifiedType VoidTy(Model.getPrimitiveType(Void, 0), {}); - TypeMap.insert({ &I, VoidTy }); - continue; - } - // Function calls in the IR might correspond to real function calls in - // the binary or to special intrinsics used by the backend, so they need - // to be handled separately - if (auto *Call = dyn_cast(&I)) { - handleCallInstruction(Cache, - Call, - ModelF, - Model, - TypeMap, - PointersOnly); - continue; - } - - // Only Call instructions can return aggregates - revng_assert(not InstType->isAggregateType()); - - // All ExtractValues should have been converted to OpaqueExtractValue - revng_assert(not isa(&I)); - - std::optional Type; - - switch (I.getOpcode()) { - - case Instruction::Load: { - auto *Load = dyn_cast(&I); - - auto It = TypeMap.find(Load->getPointerOperand()); - if (It == TypeMap.end()) - continue; - - const auto &PtrOperandType = It->second; - - // If the pointer operand is a pointer in the model, we can exploit - // this information to assign a model type to the loaded value. Note - // that this makes sense only if the pointee is itself a pointer or a - // scalar value: if we find a load of N bits from a struct pointer, we - // don't know if we are loading the entire struct or only some of its - // fields. - // TODO: inspect the model to understand if we are loading the first - // field. - if (PtrOperandType.isPointer()) { - model::QualifiedType Pointee = dropPointer(PtrOperandType); - - if (areMemOpCompatible(Pointee, *Load->getType(), Model)) - Type = Pointee; - } - - // If it's not a pointer or a scalar of the right size, just - // fallback to the LLVM type - - } break; - - case Instruction::Alloca: { - // TODO: eventually AllocaInst will be replaced by calls to - // revng_local_variable with a type annotation - llvm::Type *BaseType = cast(&I)->getAllocatedType(); - revng_assert(BaseType->isSingleValueType()); - const model::Architecture::Values &Architecture = Model.Architecture(); - Type = llvmIntToModelType(BaseType, Model).getPointerTo(Architecture); - } break; - - case Instruction::Select: { - auto *Select = dyn_cast(&I); - const auto &Op1Entry = TypeMap.find(Select->getOperand(1)); - const auto &Op2Entry = TypeMap.find(Select->getOperand(2)); - - // If the two selected values have the same type, assign that type to - // the result - if (Op1Entry != TypeMap.end() and Op2Entry != TypeMap.end() - and Op1Entry->second == Op2Entry->second) - Type = Op1Entry->second; - - } break; - - // Handle zext from i1 to i8 - case Instruction::ZExt: { - auto *ZExt = dyn_cast(&I); - - auto IsBoolZext = ZExt->getSrcTy()->getScalarSizeInBits() == 1 - and ZExt->getDestTy()->getScalarSizeInBits() == 8; - - if (not PointersOnly and IsBoolZext) { - const llvm::Value *Operand = I.getOperand(0); - - // Forward the type if there is one - auto It = TypeMap.find(Operand); - if (It != TypeMap.end()) - Type = It->second; - } - - } break; - - // Handle trunc from i8 to i1 - case Instruction::Trunc: { - auto *Trunc = dyn_cast(&I); - - auto IsBoolTrunc = Trunc->getSrcTy()->getScalarSizeInBits() == 8 - and Trunc->getDestTy()->getScalarSizeInBits() == 1; - - if (not PointersOnly and IsBoolTrunc) { - const llvm::Value *Operand = I.getOperand(0); - - // Forward the type if there is one - auto It = TypeMap.find(Operand); - if (It != TypeMap.end()) - Type = It->second; - } - - } break; - - case Instruction::BitCast: - case Instruction::Freeze: - case Instruction::IntToPtr: - case Instruction::PtrToInt: { - // Forward the type if there is one - auto It = TypeMap.find(I.getOperand(0)); - if (It != TypeMap.end()) - Type = It->second; - } break; - - default: - break; - } - + std::optional Type = initModelTypesImpl(Cache, + I, + F, + ModelF, + Model, + PointersOnly, + TypeMap, + VisitedPHIs); if (PointersOnly) { // Skip if it's not a pointer and we are only interested in pointers if (Type and Type->isPointer()) @@ -495,10 +732,11 @@ ModelTypesMap initModelTypes(FunctionMetadataCache &Cache, } else { // As a fallback, use the LLVM type to build the QualifiedType - if (not Type) - Type = llvmIntToModelType(InstType, Model); + if (not Type and I.getType()->isIntOrPtrTy()) + Type = llvmIntToModelType(I.getType(), Model); - TypeMap.insert({ &I, *Type }); + if (Type) + TypeMap.insert({ &I, *Type }); } } } @@ -514,5 +752,13 @@ ModelTypesMap initModelTypes(FunctionMetadataCache &Cache, VMA.run(Cache, &F); } - return TypeMap; + rc_return TypeMap; +} + +ModelTypesMap initModelTypes(FunctionMetadataCache &Cache, + const llvm::Function &F, + const model::Function *ModelF, + const Binary &Model, + bool PointersOnly) { + return initModelTypesImpl(Cache, F, ModelF, Model, PointersOnly); }