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

130 lines
4.4 KiB
C++

//===- Context.cpp - State Tracking 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 tracks the global states (e.g., memory) of the interpreter.
//
//===----------------------------------------------------------------------===//
#include "Context.h"
#include "llvm/Support/MathExtras.h"
namespace llvm::ubi {
Context::Context(Module &M)
: Ctx(M.getContext()), M(M), DL(M.getDataLayout()),
TLIImpl(M.getTargetTriple()) {}
Context::~Context() = default;
AnyValue Context::getConstantValueImpl(Constant *C) {
if (isa<PoisonValue>(C))
return AnyValue::getPoisonValue(*this, C->getType());
// TODO: Handle ConstantInt vector.
if (auto *CI = dyn_cast<ConstantInt>(C))
return CI->getValue();
llvm_unreachable("Unrecognized constant");
}
const AnyValue &Context::getConstantValue(Constant *C) {
auto It = ConstCache.find(C);
if (It != ConstCache.end())
return It->second;
return ConstCache.emplace(C, getConstantValueImpl(C)).first->second;
}
MemoryObject::~MemoryObject() = default;
MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
unsigned AS, MemInitKind InitKind)
: Address(Addr), Size(Size), Name(Name), AS(AS),
State(InitKind != MemInitKind::Poisoned ? MemoryObjectState::Alive
: MemoryObjectState::Dead) {
switch (InitKind) {
case MemInitKind::Zeroed:
Bytes.resize(Size, Byte{0, ByteKind::Concrete});
break;
case MemInitKind::Uninitialized:
Bytes.resize(Size, Byte{0, ByteKind::Undef});
break;
case MemInitKind::Poisoned:
Bytes.resize(Size, Byte{0, ByteKind::Poison});
break;
}
}
IntrusiveRefCntPtr<MemoryObject> Context::allocate(uint64_t Size,
uint64_t Align,
StringRef Name, unsigned AS,
MemInitKind InitKind) {
if (MaxMem != 0 && SaturatingAdd(UsedMem, Size) >= MaxMem)
return nullptr;
uint64_t AlignedAddr = alignTo(AllocationBase, Align);
auto MemObj =
makeIntrusiveRefCnt<MemoryObject>(AlignedAddr, Size, Name, AS, InitKind);
MemoryObjects[AlignedAddr] = MemObj;
AllocationBase = AlignedAddr + Size;
UsedMem += Size;
return MemObj;
}
bool Context::free(uint64_t Address) {
auto It = MemoryObjects.find(Address);
if (It == MemoryObjects.end())
return false;
UsedMem -= It->second->getSize();
It->second->markAsFreed();
MemoryObjects.erase(It);
return true;
}
Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
assert(Obj && "Cannot determine the address space of a null memory object");
return Pointer(
Obj,
APInt(DL.getPointerSizeInBits(Obj->getAddressSpace()), Obj->getAddress()),
/*Offset=*/0);
}
void MemoryObject::markAsFreed() {
State = MemoryObjectState::Freed;
Bytes.clear();
}
void MemoryObject::writeRawBytes(uint64_t Offset, const void *Data,
uint64_t Length) {
assert(SaturatingAdd(Offset, Length) <= Size && "Write out of bounds");
const uint8_t *ByteData = static_cast<const uint8_t *>(Data);
for (uint64_t I = 0; I < Length; ++I)
Bytes[Offset + I].set(ByteData[I]);
}
void MemoryObject::writeInteger(uint64_t Offset, const APInt &Int,
const DataLayout &DL) {
uint64_t BitWidth = Int.getBitWidth();
uint64_t IntSize = divideCeil(BitWidth, 8);
assert(SaturatingAdd(Offset, IntSize) <= Size && "Write out of bounds");
for (uint64_t I = 0; I < IntSize; ++I) {
uint64_t ByteIndex = DL.isLittleEndian() ? I : (IntSize - 1 - I);
uint64_t Bits = std::min(BitWidth - ByteIndex * 8, uint64_t(8));
Bytes[Offset + I].set(Int.extractBitsAsZExtValue(Bits, ByteIndex * 8));
}
}
void MemoryObject::writeFloat(uint64_t Offset, const APFloat &Float,
const DataLayout &DL) {
writeInteger(Offset, Float.bitcastToAPInt(), DL);
}
void MemoryObject::writePointer(uint64_t Offset, const Pointer &Ptr,
const DataLayout &DL) {
writeInteger(Offset, Ptr.address(), DL);
// TODO: provenance
}
} // namespace llvm::ubi