Yingwei Zheng a29f0dd096
[llubi] Add initial support for llubi (#180022)
This patch implements the initial support for upstreaming
[llubi](https://github.com/dtcxzyw/llvm-ub-aware-interpreter). It only
provides the minimal functionality to run a simple main function. I hope
we can focus on the interface design in this PR, rather than trivial
implementations for each instruction.
RFC link:
https://discourse.llvm.org/t/rfc-upstreaming-llvm-ub-aware-interpreter/89645

Excluding the driver `llubi.cpp`, this patch contains three components
for better decoupling:
+ `Value.h/cpp`: Value representation
+ `Context.h/cpp`: Global state management (e.g., memory) and
interpreter configuration
+ `Interpreter.cpp`: The main interpreter loop

Compared to the out-of-tree version, the major differences are listed
below:
+ The interpreter logic always returns the control to its caller, i.e.,
it never calls `exit/abort` when immediate UBs are triggered.
+ `EventHandler` provides an interface to dump the trace. It also allows
callers to inspect the actual value and verify the correctness of
analysis passes (e.g, KnownBits/SCEV).
+ The context is designed to be reentrant. That is, you can call
`runFunction` multiple times. But its usefulness remains in doubt due to
side effects made by previous calls.
+ `runFunction` handles function calls with a loop, instead of calling
itself recursively. This makes it no longer bounded by the stack depth.
+ Uninitialized memory is planned to be approximated by returning random
values each time an uninitialized byte is loaded.
2026-02-10 01:54:34 +08:00

231 lines
6.3 KiB
C++

//===- Value.cpp - Value Representation for llubi -------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements utility functions for the value representation.
//
//===----------------------------------------------------------------------===//
#include "Value.h"
#include "Context.h"
#include "llvm/ADT/SmallString.h"
namespace llvm::ubi {
void Pointer::print(raw_ostream &OS) const {
SmallString<32> AddrStr;
Address.toStringUnsigned(AddrStr, 16);
OS << "ptr 0x" << AddrStr << " [";
if (Obj) {
OS << Obj->getName();
if (Offset)
OS << " + " << Offset;
} else {
OS << "dangling";
}
OS << "]";
}
AnyValue Pointer::null(unsigned BitWidth) {
return AnyValue(Pointer(nullptr, APInt::getZero(BitWidth), 0));
}
void AnyValue::print(raw_ostream &OS) const {
switch (Kind) {
case StorageKind::Integer:
if (IntVal.getBitWidth() == 1) {
OS << (IntVal.getBoolValue() ? "T" : "F");
break;
}
OS << "i" << IntVal.getBitWidth() << ' ' << IntVal;
break;
case StorageKind::Float:
OS << FloatVal;
break;
case StorageKind::Pointer:
PtrVal.print(OS);
break;
case StorageKind::Poison:
OS << "poison";
break;
case StorageKind::None:
OS << "none";
break;
case StorageKind::Aggregate:
OS << "{ ";
for (size_t I = 0, E = AggVal.size(); I != E; ++I) {
if (I != 0)
OS << ", ";
AggVal[I].print(OS);
}
OS << " }";
break;
}
}
void AnyValue::destroy() {
switch (Kind) {
case StorageKind::Integer:
IntVal.~APInt();
break;
case StorageKind::Float:
FloatVal.~APFloat();
break;
case StorageKind::Pointer:
PtrVal.~Pointer();
break;
case StorageKind::Poison:
case StorageKind::None:
break;
case StorageKind::Aggregate:
AggVal.~vector();
break;
}
}
AnyValue::AnyValue(const AnyValue &Other) : Kind(Other.Kind) {
switch (Other.Kind) {
case StorageKind::Integer:
new (&IntVal) APInt(Other.IntVal);
break;
case StorageKind::Float:
new (&FloatVal) APFloat(Other.FloatVal);
break;
case StorageKind::Pointer:
new (&PtrVal) Pointer(Other.PtrVal);
break;
case StorageKind::Poison:
case StorageKind::None:
break;
case StorageKind::Aggregate:
new (&AggVal) std::vector<AnyValue>(Other.AggVal);
break;
}
}
AnyValue::AnyValue(AnyValue &&Other) : Kind(Other.Kind) {
switch (Other.Kind) {
case StorageKind::Integer:
new (&IntVal) APInt(std::move(Other.IntVal));
break;
case StorageKind::Float:
new (&FloatVal) APFloat(std::move(Other.FloatVal));
break;
case StorageKind::Pointer:
new (&PtrVal) Pointer(std::move(Other.PtrVal));
break;
case StorageKind::Poison:
case StorageKind::None:
break;
case StorageKind::Aggregate:
new (&AggVal) std::vector<AnyValue>(std::move(Other.AggVal));
break;
}
}
AnyValue &AnyValue::operator=(const AnyValue &Other) {
if (&Other == this)
return *this;
destroy();
Kind = Other.Kind;
switch (Other.Kind) {
case StorageKind::Integer:
new (&IntVal) APInt(Other.IntVal);
break;
case StorageKind::Float:
new (&FloatVal) APFloat(Other.FloatVal);
break;
case StorageKind::Pointer:
new (&PtrVal) Pointer(Other.PtrVal);
break;
case StorageKind::Poison:
case StorageKind::None:
break;
case StorageKind::Aggregate:
new (&AggVal) std::vector<AnyValue>(Other.AggVal);
break;
}
return *this;
}
AnyValue &AnyValue::operator=(AnyValue &&Other) {
if (&Other == this)
return *this;
destroy();
Kind = Other.Kind;
switch (Other.Kind) {
case StorageKind::Integer:
new (&IntVal) APInt(std::move(Other.IntVal));
break;
case StorageKind::Float:
new (&FloatVal) APFloat(std::move(Other.FloatVal));
break;
case StorageKind::Pointer:
new (&PtrVal) Pointer(std::move(Other.PtrVal));
break;
case StorageKind::Poison:
case StorageKind::None:
break;
case StorageKind::Aggregate:
new (&AggVal) std::vector<AnyValue>(std::move(Other.AggVal));
break;
}
return *this;
}
AnyValue AnyValue::getPoisonValue(Context &Ctx, Type *Ty) {
if (Ty->isFloatingPointTy() || Ty->isIntegerTy() || Ty->isPointerTy())
return AnyValue::poison();
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(NumElements, AnyValue::poison()));
}
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
uint64_t NumElements = ArrTy->getNumElements();
return AnyValue(std::vector<AnyValue>(
NumElements, getPoisonValue(Ctx, ArrTy->getElementType())));
}
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
std::vector<AnyValue> Elements;
Elements.reserve(StructTy->getNumElements());
for (uint32_t I = 0, E = StructTy->getNumElements(); I != E; ++I)
Elements.push_back(getPoisonValue(Ctx, StructTy->getElementType(I)));
return AnyValue(std::move(Elements));
}
llvm_unreachable("Unsupported type");
}
AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
if (Ty->isIntegerTy())
return AnyValue(APInt::getZero(Ty->getIntegerBitWidth()));
if (Ty->isFloatingPointTy())
return AnyValue(APFloat::getZero(Ty->getFltSemantics()));
if (Ty->isPointerTy())
return Pointer::null(
Ctx.getDataLayout().getPointerSizeInBits(Ty->getPointerAddressSpace()));
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(
NumElements, getNullValue(Ctx, VecTy->getElementType())));
}
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
uint64_t NumElements = ArrTy->getNumElements();
return AnyValue(std::vector<AnyValue>(
NumElements, getNullValue(Ctx, ArrTy->getElementType())));
}
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
std::vector<AnyValue> Elements;
Elements.reserve(StructTy->getNumElements());
for (uint32_t I = 0, E = StructTy->getNumElements(); I != E; ++I)
Elements.push_back(getNullValue(Ctx, StructTy->getElementType(I)));
return AnyValue(std::move(Elements));
}
llvm_unreachable("Unsupported type");
}
} // namespace llvm::ubi