Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

384 lines
14 KiB
C++

//===--- Context.h - State Tracking for llubi -------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_TOOLS_LLUBI_CONTEXT_H
#define LLVM_TOOLS_LLUBI_CONTEXT_H
#include "Value.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/AsmParser/AsmParserContext.h"
#include "llvm/IR/FPEnv.h"
#include "llvm/IR/Module.h"
#include <map>
#include <optional>
#include <random>
namespace llvm::ubi {
enum class MemInitKind {
Zeroed,
Uninitialized,
Poisoned,
};
enum class MemAllocKind {
Global,
BlockAddress,
Stack,
Malloc,
New,
NewArray,
};
enum class MemoryObjectState {
// This memory object is accessible.
// Valid transitions:
// -> Dead (after the end of lifetime of an alloca)
// -> Freed (after free is called on a heap object)
Alive,
// This memory object is out of lifetime. It is OK to perform
// operations that do not access its content, e.g., getelementptr.
// Otherwise, an immediate UB occurs.
// Valid transition:
// -> Alive (after the start of lifetime of an alloca)
Dead,
// This heap memory object has been freed. Any access to it
// causes immediate UB. Like dead objects, it is still possible to
// perform operations that do not access its content.
Freed,
};
enum class UndefValueBehavior {
NonDeterministic, // Each use of the undef value can yield different results.
Zero, // All uses of the undef value yield zero.
};
enum class NaNPropagationBehavior {
NonDeterministic, // Non-deterministically choose from valid NaN results
PreferredNaN, // The quiet bit is set and the payload is all-zero
QuietingNaN, // The quiet bit is set and the payload is copied from any input
// operand that is a NaN
UnchangedNaN, // The quiet bit and payload are copied from any input operand
// that is a NaN
TargetSpecificNaN // The quiet bit is set and the payload is picked from a
// known target-specific set of "extra" possible NaN
// payloads
};
struct ProgramExitInfo {
enum class ProgramExitKind {
// Program exited via a normal return
Returned,
// Program exited with an interpreter error (UB/Unsupported
// instruction/etc.)
Failed,
// Program exited via a call to exit()
Exited,
// Program exited via a call to abort()
Aborted,
// Program exited via a call to terminate()
Terminated,
};
ProgramExitKind Kind;
uint64_t ExitCode;
explicit ProgramExitInfo(ProgramExitKind Kind, uint64_t ExitCode)
: Kind(Kind), ExitCode(ExitCode) {}
bool isExitedByLibcall() const {
return Kind == ProgramExitKind::Exited ||
Kind == ProgramExitKind::Aborted ||
Kind == ProgramExitKind::Terminated;
}
};
class MemoryObject : public RefCountedBase<MemoryObject> {
uint64_t Address;
uint64_t Size;
SmallVector<Byte, 8> Bytes;
StringRef Name;
unsigned AS;
MemoryObjectState State;
MemAllocKind AllocKind;
bool IsConstant = false;
bool IsIRGlobalValue = false;
// Tagged provenances related to this memory object.
// It is used to erasing the tags after the memory object is freed.
SmallVector<APInt> AssociatedTags;
friend class Context;
public:
MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name, unsigned AS,
MemInitKind InitKind, MemAllocKind AllocKind,
bool IsIRGlobalValue = false);
MemoryObject(const MemoryObject &) = delete;
MemoryObject(MemoryObject &&) = delete;
MemoryObject &operator=(const MemoryObject &) = delete;
MemoryObject &operator=(MemoryObject &&) = delete;
~MemoryObject();
uint64_t getAddress() const { return Address; }
uint64_t getSize() const { return Size; }
StringRef getName() const { return Name; }
unsigned getAddressSpace() const { return AS; }
MemoryObjectState getState() const { return State; }
void setState(MemoryObjectState S) { State = S; }
MemAllocKind getAllocKind() const { return AllocKind; }
bool isIRGlobalValue() const { return IsIRGlobalValue; }
bool isConstant() const { return IsConstant; }
void setIsConstant(bool C) { IsConstant = C; }
bool inBounds(const APInt &NewAddr) const {
return NewAddr.uge(Address) && NewAddr.ule(Address + Size);
}
Byte &operator[](uint64_t Offset) {
assert(Offset < Size && "Offset out of bounds");
return Bytes[Offset];
}
ArrayRef<Byte> getBytes() const { return Bytes; }
MutableArrayRef<Byte> getBytes() { return Bytes; }
bool isGlobal() const;
bool isStackAllocated() const;
bool isHeapAllocated() const;
};
/// An interface for handling events and managing outputs during interpretation.
/// If the handler returns false from any of the methods, the interpreter will
/// stop execution immediately.
class EventHandler {
public:
virtual ~EventHandler() = default;
virtual bool onInstructionExecuted(Instruction &I, const AnyValue &Result) {
return true;
}
virtual void onError(StringRef Msg) {}
virtual void onUnrecognizedInstruction(Instruction &I) {}
virtual void onImmediateUB(StringRef Msg) {}
virtual bool onBBJump(Instruction &I, BasicBlock &To) { return true; }
virtual bool onFunctionEntry(Function &F, ArrayRef<AnyValue> Args,
CallBase *CallSite) {
return true;
}
virtual bool onFunctionExit(Function &F, const AnyValue &RetVal) {
return true;
}
virtual void onProgramExit(const ProgramExitInfo &ExitInfo) {}
virtual bool onPrint(StringRef Msg) {
outs() << Msg;
outs().flush();
return true;
}
};
/// Endianness aware accessor for bytes.
template <typename ArrayRefT> class BytesView {
ArrayRefT Bytes;
bool IsLittleEndian;
public:
explicit BytesView(ArrayRefT Ref, const DataLayout &DL)
: Bytes(Ref), IsLittleEndian(DL.isLittleEndian()) {}
auto &operator[](uint32_t Index) {
return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
}
};
using ConstBytesView = BytesView<ArrayRef<Byte>>;
using MutableBytesView = BytesView<MutableArrayRef<Byte>>;
/// The global context for the interpreter.
/// It tracks global state such as heap memory objects and floating point
/// environment.
class Context {
// Module
LLVMContext &Ctx;
Module &M;
const AsmParserContext *ParserContext;
const DataLayout &DL;
const TargetLibraryInfoImpl TLIImpl;
// Configuration
uint64_t MaxMem = 0;
uint32_t VScale = 4;
uint32_t MaxSteps = 0;
uint32_t MaxStackDepth = 256;
bool Deterministic = false;
UndefValueBehavior UndefBehavior = UndefValueBehavior::NonDeterministic;
NaNPropagationBehavior NaNBehavior = NaNPropagationBehavior::NonDeterministic;
bool FusedMultiplyAdd = false;
std::mt19937_64 Rng;
/// Always returns a random APInt value. It is not controlled by
/// Deterministic.
APInt generateRandomAPInt(uint32_t BitWidth);
// Memory
uint64_t UsedMem = 0;
// The addresses of memory objects are monotonically increasing.
// For now we don't model the behavior of address reuse, which is common
// with stack coloring.
uint64_t AllocationBase = 8;
// All live memory objects.
DenseMap<uint64_t, IntrusiveRefCntPtr<MemoryObject>> MemoryObjects;
// Mapping from tags to provenances. Tags are lazily generated when a
// pointer is captured by memory.
DenseMap<APInt, IntrusiveRefCntPtr<Provenance>> TaggedProvenances;
// TODO: Maintains a global list of 'exposed' provenances. This is used to
// convert an address back to a pointer with a previously exposed provenance.
/// Get the tag for the given pointer provenance.
APInt getTag(uint32_t BitWidth, Provenance &Prov);
AnyValue fromBytes(ConstBytesView Bytes, Type *Ty, uint32_t OffsetInBits,
bool CheckPaddingBits, bool *ContainsUndefinedBits);
void toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
MutableBytesView Bytes, bool PaddingBits);
// Constants
// Use std::map to avoid iterator/reference invalidation.
std::map<Constant *, AnyValue> ConstCache;
DenseMap<Function *, Pointer> FuncAddrMap;
DenseMap<BasicBlock *, Pointer> BlockAddrMap;
DenseMap<uint64_t, std::pair<Function *, IntrusiveRefCntPtr<MemoryObject>>>
ValidFuncTargets;
DenseMap<uint64_t, std::pair<BasicBlock *, IntrusiveRefCntPtr<MemoryObject>>>
ValidBlockTargets;
DenseMap<GlobalVariable *, Pointer> GlobalAddrMap;
std::optional<AnyValue> getConstantValueImpl(Constant *C);
// Floating-point environment
RoundingMode CurrentRoundingMode = RoundingMode::NearestTiesToEven;
fp::ExceptionBehavior CurrentExceptionBehavior =
fp::ExceptionBehavior::ebIgnore;
// TODO: errno
public:
explicit Context(Module &M, const AsmParserContext *ParserContext);
Context(const Context &) = delete;
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
Context &operator=(Context &&) = delete;
~Context();
void setMemoryLimit(uint64_t Max) { MaxMem = Max; }
void setVScale(uint32_t VS) { VScale = VS; }
void setMaxSteps(uint32_t MS) { MaxSteps = MS; }
void setMaxStackDepth(uint32_t Depth) { MaxStackDepth = Depth; }
void setFusedMultiplyAdd(bool F) { FusedMultiplyAdd = F; }
uint64_t getMemoryLimit() const { return MaxMem; }
uint32_t getVScale() const { return VScale; }
uint32_t getMaxSteps() const { return MaxSteps; }
uint32_t getMaxStackDepth() const { return MaxStackDepth; }
void setDeterministic(bool D) { Deterministic = D; }
bool isDeterministic() const { return Deterministic; }
bool mayUseNonDeterminism() const { return !Deterministic; }
UndefValueBehavior getEffectiveUndefValueBehavior() const;
NaNPropagationBehavior getEffectiveNaNPropagationBehavior() const;
bool fuseMultiplyAdd() const { return FusedMultiplyAdd; }
void setUndefValueBehavior(UndefValueBehavior UB) { UndefBehavior = UB; }
void setNaNPropagationBehavior(NaNPropagationBehavior NaNBehav) {
NaNBehavior = NaNBehav;
}
void reseed(uint32_t Seed) { Rng.seed(Seed); }
LLVMContext &getContext() const { return Ctx; }
Module &getModule() const { return M; }
const AsmParserContext *getParserContext() const { return ParserContext; }
const DataLayout &getDataLayout() const { return DL; }
const Triple &getTargetTriple() const { return M.getTargetTriple(); }
const TargetLibraryInfoImpl &getTLIImpl() const { return TLIImpl; }
/// Get the effective vector length for a vector type.
uint32_t getEVL(ElementCount EC) const {
if (EC.isScalable())
return VScale * EC.getKnownMinValue();
return EC.getFixedValue();
}
/// The result is multiplied by VScale for scalable type sizes.
uint64_t getEffectiveTypeSize(TypeSize Size) const {
if (Size.isScalable())
return VScale * Size.getKnownMinValue();
return Size.getFixedValue();
}
/// Returns DL.getTypeAllocSize/getTypeStoreSize for the given type.
/// An exception to this is that for scalable vector types, the size is
/// computed as if the vector has getEVL(ElementCount) elements.
uint64_t getEffectiveTypeAllocSize(Type *Ty);
uint64_t getEffectiveTypeStoreSize(Type *Ty);
const AnyValue *getConstantValue(Constant *C);
IntrusiveRefCntPtr<MemoryObject> allocate(uint64_t Size, uint64_t Align,
StringRef Name, unsigned AS,
MemInitKind InitKind,
MemAllocKind AllocKind,
bool IsIRGlobalValue = false);
bool free(const MemoryObject &Obj);
/// Derive a pointer from a memory object with offset 0.
/// Please use Pointer's interface for further manipulations.
Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
/// Convert byte sequence to a value of the given type. Uninitialized bits are
/// flushed according to the options.
/// If \p ContainsUndefinedBits is provided, it will be set to true when there
/// are poison or undef bits in the value (i.e., padding bits are ignored).
AnyValue fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
bool *ContainsUndefinedBits = nullptr);
/// Convert a value to byte sequence. Padding bits are set to zero.
void toBytes(const AnyValue &Val, Type *Ty, MutableArrayRef<Byte> Bytes);
/// Direct memory load without checks.
AnyValue load(MemoryObject &MO, uint64_t Offset, Type *ValTy,
bool *ContainsUndefinedBits = nullptr);
/// Direct memory store without checks.
void store(MemoryObject &MO, uint64_t Offset, const AnyValue &Val,
Type *ValTy);
void storeRawBytes(MemoryObject &MO, uint64_t Offset, const void *Data,
uint64_t Size);
/// Freeze the value in-place.
void freeze(AnyValue &Val, Type *Ty);
Function *getTargetFunction(const Pointer &Ptr);
BasicBlock *getTargetBlock(const Pointer &Ptr);
/// Initialize global variables and function/block objects. This function
/// should be called before executing any function. Returns false if the
/// initialization fails (e.g., the memory limit is exceeded during
/// initialization).
bool initGlobalValues();
/// Execute the function \p F with arguments \p Args, and store the return
/// value in \p RetVal if the function is not void.
/// Returns a `ProgramExitInfo` indicating how the program finished:
/// Kind = Returned: The program executed successfully and returned normally.
/// Kind = Failed: The interpreter encountered an error and could not execute
/// the program.
/// Kind = Exited/Aborted/Terminated: The program ended via an
/// explicit call to `exit()`, `abort()`, or `terminate()`.
ProgramExitInfo runFunction(Function &F, ArrayRef<AnyValue> Args,
AnyValue &RetVal, EventHandler &Handler);
RoundingMode getCurrentRoundingMode() const;
fp::ExceptionBehavior getCurrentExceptionBehavior() const;
void setCurrentRoundingMode(RoundingMode RM);
void setCurrentExceptionBehavior(fp::ExceptionBehavior EB);
bool isDefaultFPEnv() const;
bool getRandomBool();
uint64_t getRandomUInt64();
};
} // namespace llvm::ubi
#endif