From 93f90607bb64d4f7f7763663e283f0058b6c47ad Mon Sep 17 00:00:00 2001 From: arti Date: Tue, 10 Nov 2026 17:32:37 +0000 Subject: [PATCH] tools: add tms-bitaddr GEP scale and normalize tool --- tools/tms-bitaddr/bitscale.cpp | 316 +++++++++++++++++++++++++++ tools/tms-bitaddr/build.sh | 8 + tools/tms-bitaddr/tests/const-gep.ll | 8 + tools/tms-bitaddr/tests/normalize.ll | 8 + tools/tms-bitaddr/tests/run.sh | 8 + tools/tms-bitaddr/tests/scale.ll | 8 + 6 files changed, 356 insertions(+) create mode 100644 tools/tms-bitaddr/bitscale.cpp create mode 100755 tools/tms-bitaddr/build.sh create mode 100644 tools/tms-bitaddr/tests/const-gep.ll create mode 100644 tools/tms-bitaddr/tests/normalize.ll create mode 100755 tools/tms-bitaddr/tests/run.sh create mode 100644 tools/tms-bitaddr/tests/scale.ll diff --git a/tools/tms-bitaddr/bitscale.cpp b/tools/tms-bitaddr/bitscale.cpp new file mode 100644 index 0000000..bd2b11c --- /dev/null +++ b/tools/tms-bitaddr/bitscale.cpp @@ -0,0 +1,316 @@ +// bitscale -- throwaway spike tool. +// +// Rewrites every non-constant getelementptr so that its byte offset is +// multiplied by 8. The result is an IR in which every pointer value is a +// *bit* address, while loads/stores still carry their normal LLVM byte-sized +// types. A real TMS34010 backend would lower "load i8 at bit address A" as +// MOVB [A] and "load i32 at bit address A" as MOVE [A],FS0-in-32-bit-mode. +// +// Build: +// clang++-19 -std=c++17 $(llvm-config-19 --cxxflags) bitscale.cpp \ +// $(llvm-config-19 --ldflags --libs core irreader support) -o bitscale + +#include "llvm/IR/Constants.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/GetElementPtrTypeIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Operator.h" +#include "llvm/IRReader/IRReader.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include + +using namespace llvm; + +static unsigned GepShift = 3; // 3 for --scale, 0 for --normalize + +struct Stats { + unsigned geps = 0; + unsigned const_geps = 0; + unsigned vector_geps = 0; + unsigned allocas = 0; + unsigned memintrinsics = 0; + unsigned vaarg = 0; + unsigned byval_args = 0; + unsigned indirect_calls = 0; + unsigned loads = 0; + unsigned stores = 0; + unsigned ptr_subs = 0; + unsigned const_ptr_subs = 0; +}; + +static void count_constants(Value *V, Stats &S, unsigned depth = 0) +{ + if (!V || depth > 64) + return; + auto *C = dyn_cast(V); + if (!C) + return; + if (auto *CE = dyn_cast(C)) { + if (CE->getOpcode() == Instruction::Sub) { + auto *L = dyn_cast(CE->getOperand(0)); + auto *R = dyn_cast(CE->getOperand(1)); + if (L && R && L->getOpcode() == Instruction::PtrToInt && + R->getOpcode() == Instruction::PtrToInt) + ++S.const_ptr_subs; + } + } + if (auto *GEP = dyn_cast(C)) { + ++S.const_geps; + count_constants(GEP->getPointerOperand(), S, depth + 1); + for (auto &Op : GEP->indices()) + count_constants(Op.get(), S, depth + 1); + return; + } + for (auto &Op : C->operands()) + count_constants(Op.get(), S, depth + 1); +} + +static void count_constant_geps(const Constant *C, Stats &S, unsigned depth = 0) +{ + count_constants(const_cast(C), S, depth); +} + +static Stats collect(Module &M) +{ + Stats S; + for (Function &F : M) { + for (Argument &A : F.args()) + if (A.hasAttribute(Attribute::ByVal)) + ++S.byval_args; + for (BasicBlock &BB : F) { + for (Instruction &I : BB) { + if (auto *GEP = dyn_cast(&I)) { + if (GEP->getType()->isVectorTy()) + ++S.vector_geps; + else + ++S.geps; + } + if (isa(&I)) + ++S.allocas; + if (isa(&I)) + ++S.loads; + if (isa(&I)) + ++S.stores; + if (auto *BO = dyn_cast(&I)) { + if (BO->getOpcode() == Instruction::Sub && + isa(BO->getOperand(0)) && + isa(BO->getOperand(1))) + ++S.ptr_subs; + } + if (auto *CB = dyn_cast(&I)) { + if (Function *Callee = CB->getCalledFunction()) { + if (Callee->isIntrinsic()) { + StringRef N = Callee->getName(); + if (N.starts_with("llvm.memcpy") || N.starts_with("llvm.memmove") || + N.starts_with("llvm.memset")) + ++S.memintrinsics; + if (N.starts_with("llvm.va_start") || N.starts_with("llvm.va_arg")) + ++S.vaarg; + } + } else { + ++S.indirect_calls; + } + for (unsigned i = 0; i < CB->arg_size(); ++i) + if (CB->paramHasAttr(i, Attribute::ByVal)) + ++S.byval_args; + } + for (Value *Op : I.operands()) + count_constants(Op, S); + } + } + } + for (GlobalVariable &G : M.globals()) + if (G.hasInitializer()) + count_constant_geps(G.getInitializer(), S); + return S; +} + +static bool scale_constant_gep(ConstantExpr *CE, const DataLayout &DL) +{ + if (CE->getType()->isVectorTy()) + return false; + + auto *GEP = cast(CE); + SmallVector Indices; + for (auto &Op : GEP->indices()) + Indices.push_back(Op.get()); + for (Value *Idx : Indices) + if (!isa(Idx)) + return false; // cannot fold a non-constant constant-expression GEP + + int64_t Off = DL.getIndexedOffsetInType(GEP->getSourceElementType(), Indices); + Constant *Base = cast(GEP->getPointerOperand()); + Constant *BaseInt = ConstantExpr::getPtrToInt(Base, Type::getInt32Ty(CE->getContext())); + Constant *BitOff = ConstantInt::get( + Type::getInt32Ty(CE->getContext()), Off * (1u << GepShift)); + Constant *BitAddr = ConstantExpr::getAdd(BaseInt, BitOff); + Constant *New = ConstantExpr::getIntToPtr(BitAddr, CE->getType()); + CE->replaceAllUsesWith(New); + return true; +} + +// base_int + (byte offset computed from the GEP's indices) << 3 +static bool scale_gep(GetElementPtrInst *GEP) +{ + if (GEP->getType()->isVectorTy()) + return false; + Value *Base = GEP->getPointerOperand(); + Type *PtrTy = GEP->getType(); + const DataLayout &DL = GEP->getModule()->getDataLayout(); + + IRBuilder<> B(GEP); + Value *BaseInt = B.CreatePtrToInt(Base, B.getInt32Ty(), "bit.base"); + Value *ByteOff = ConstantInt::get(B.getInt32Ty(), 0); + + for (auto GTI = gep_type_begin(GEP), GTE = gep_type_end(GEP); GTI != GTE; ++GTI) { + Value *Idx = GTI.getOperand(); + Value *Idx32 = B.CreateSExtOrTrunc(Idx, B.getInt32Ty()); + if (StructType *STy = GTI.getStructTypeOrNull()) { + unsigned FieldNo = cast(Idx)->getZExtValue(); + uint64_t FieldOff = DL.getStructLayout(STy)->getElementOffset(FieldNo); + ByteOff = B.CreateAdd(ByteOff, ConstantInt::get(B.getInt32Ty(), FieldOff)); + } else { + uint64_t Stride = GTI.getSequentialElementStride(DL); + Value *Scaled = B.CreateMul(Idx32, ConstantInt::get(B.getInt32Ty(), Stride)); + ByteOff = B.CreateAdd(ByteOff, Scaled); + } + } + + Value *BitOff = GepShift ? B.CreateShl(ByteOff, GepShift, "bit.off") + : ByteOff; + Value *BitAddr = B.CreateAdd(BaseInt, BitOff, "bit.addr"); + Value *NewPtr = B.CreateIntToPtr(BitAddr, PtrTy, "bit.ptr"); + + GEP->replaceAllUsesWith(NewPtr); + GEP->eraseFromParent(); + return true; +} + +static void collect_const_geps(Value *V, std::vector &Out, + unsigned depth = 0) +{ + if (!V || depth > 64) + return; + auto *C = dyn_cast(V); + if (!C) + return; + if (auto *CE = dyn_cast(C)) { + if (CE->getOpcode() == Instruction::GetElementPtr && !CE->getType()->isVectorTy()) { + Out.push_back(CE); + return; + } + } + for (auto &Op : C->operands()) + collect_const_geps(Op.get(), Out, depth + 1); +} + +int main(int argc, char **argv) +{ + if (argc < 3) { + errs() << "usage: " << argv[0] + << " [--scale|--normalize] input.ll [output.ll]\n"; + return 2; + } + int Arg = 1; + if (StringRef(argv[Arg]) == "--normalize") { + GepShift = 0; + ++Arg; + } else if (StringRef(argv[Arg]) == "--scale") { + GepShift = 3; + ++Arg; + } + int Remaining = argc - Arg; + if (Remaining != 1 && Remaining != 2) { + errs() << "usage: " << argv[0] + << " [--scale|--normalize] input.ll [output.ll]\n"; + return 2; + } + + LLVMContext Ctx; + SMDiagnostic Err; + std::unique_ptr M = parseIRFile(argv[Arg], Err, Ctx); + if (!M) { + Err.print(argv[0], errs()); + return 1; + } + + Stats Before = collect(*M); + + // Constant-expression GEPs (e.g. `"hello" + 2`) cannot be replaced by an + // instruction, so rewrite them as constant inttoptr(add(ptrtoint, 8*off)). + unsigned ConstScaled = 0; + const DataLayout &DL = M->getDataLayout(); + bool Changed = true; + while (Changed) { + Changed = false; + std::vector Work; + for (GlobalVariable &G : M->globals()) + if (G.hasInitializer()) + collect_const_geps(G.getInitializer(), Work); + for (Function &F : *M) + for (BasicBlock &BB : F) + for (Instruction &I : BB) + for (Value *Op : I.operands()) + collect_const_geps(Op, Work); + + std::set Seen; + for (ConstantExpr *CE : Work) { + if (!Seen.insert(CE).second) + continue; + if (scale_constant_gep(CE, DL)) { + ++ConstScaled; + Changed = true; + } + } + } + + unsigned Scaled = 0; + for (Function &F : *M) { + std::vector Work; + for (BasicBlock &BB : F) + for (Instruction &I : BB) + if (auto *GEP = dyn_cast(&I)) + Work.push_back(GEP); + for (GetElementPtrInst *GEP : Work) + if (scale_gep(GEP)) + ++Scaled; + } + Stats After = collect(*M); + + errs() << (GepShift ? "bitscale: scaled " : "bitscale: normalized ") + << Scaled << " GEPs, " << ConstScaled << " constant GEPs\n"; + errs() << " before: geps=" << Before.geps << " const_geps=" << Before.const_geps + << " vector_geps=" << Before.vector_geps << " allocas=" << Before.allocas + << " memintrinsics=" << Before.memintrinsics << " va=" << Before.vaarg + << " byval=" << Before.byval_args << " indirect=" << Before.indirect_calls + << " loads=" << Before.loads << " stores=" << Before.stores + << " ptr_subs=" << Before.ptr_subs << " const_ptr_subs=" << Before.const_ptr_subs << "\n"; + errs() << " after: geps=" << After.geps << " const_geps=" << After.const_geps + << " vector_geps=" << After.vector_geps << " allocas=" << After.allocas + << " memintrinsics=" << After.memintrinsics << " va=" << After.vaarg + << " byval=" << After.byval_args << " indirect=" << After.indirect_calls + << " loads=" << After.loads << " stores=" << After.stores + << " ptr_subs=" << After.ptr_subs << " const_ptr_subs=" << After.const_ptr_subs << "\n"; + + if (Remaining == 2) { + std::error_code EC; + raw_fd_ostream Out(argv[Arg + 1], EC); + if (EC) { + errs() << "cannot write " << argv[Arg + 1] << ": " << EC.message() + << "\n"; + return 1; + } + M->print(Out, nullptr); + } else { + M->print(outs(), nullptr); + } + return 0; +} diff --git a/tools/tms-bitaddr/build.sh b/tools/tms-bitaddr/build.sh new file mode 100755 index 0000000..68ccd09 --- /dev/null +++ b/tools/tms-bitaddr/build.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu +ROOT=$(cd "$(dirname "$0")/../.." && pwd) +LLVM_CONFIG=${LLVM_CONFIG:-$ROOT/build/llvm-build/bin/llvm-config} +cd "$(dirname "$0")" +c++ -std=c++17 $("$LLVM_CONFIG" --cxxflags) bitscale.cpp \ + $("$LLVM_CONFIG" --ldflags --libs core irreader support --system-libs) \ + -o tms-bitaddr diff --git a/tools/tms-bitaddr/tests/const-gep.ll b/tools/tms-bitaddr/tests/const-gep.ll new file mode 100644 index 0000000..ff3db67 --- /dev/null +++ b/tools/tms-bitaddr/tests/const-gep.ll @@ -0,0 +1,8 @@ +; RUN: %tms-bitaddr --scale %s | FileCheck %s +@g = global [4 x i8] zeroinitializer +@s = global ptr getelementptr ([4 x i8], ptr @g, i32 0, i32 2) +define ptr @c() { + ret ptr getelementptr ([4 x i8], ptr @g, i32 0, i32 2) +} +; CHECK: @s = global ptr inttoptr (i32 add (i32 ptrtoint (ptr @g to i32), i32 16) to ptr) +; CHECK: inttoptr (i32 add (i32 ptrtoint (ptr @g to i32), i32 16) to ptr) diff --git a/tools/tms-bitaddr/tests/normalize.ll b/tools/tms-bitaddr/tests/normalize.ll new file mode 100644 index 0000000..ec0cd99 --- /dev/null +++ b/tools/tms-bitaddr/tests/normalize.ll @@ -0,0 +1,8 @@ +; RUN: %tms-bitaddr --normalize %s | FileCheck %s +define ptr @n(ptr %p) { + %q = getelementptr i8, ptr %p, i32 16 + ret ptr %q +} +; CHECK-LABEL: @n +; CHECK: %bit.addr = add i32 %bit.base, 16 +; CHECK-NOT: getelementptr diff --git a/tools/tms-bitaddr/tests/run.sh b/tools/tms-bitaddr/tests/run.sh new file mode 100755 index 0000000..1887d9c --- /dev/null +++ b/tools/tms-bitaddr/tests/run.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu +ROOT=$(cd "$(dirname "$0")/.." && pwd) +TOOL=${TMS_BITADDR:-$ROOT/tms-bitaddr} +FILECHECK=${FILECHECK:-$ROOT/../../build/llvm-build/bin/FileCheck} +"$TOOL" --scale "$ROOT/tests/scale.ll" | "$FILECHECK" "$ROOT/tests/scale.ll" +"$TOOL" --scale "$ROOT/tests/const-gep.ll" | "$FILECHECK" "$ROOT/tests/const-gep.ll" +"$TOOL" --normalize "$ROOT/tests/normalize.ll" | "$FILECHECK" "$ROOT/tests/normalize.ll" diff --git a/tools/tms-bitaddr/tests/scale.ll b/tools/tms-bitaddr/tests/scale.ll new file mode 100644 index 0000000..b93071e --- /dev/null +++ b/tools/tms-bitaddr/tests/scale.ll @@ -0,0 +1,8 @@ +; RUN: %tms-bitaddr --scale %s | FileCheck %s +define ptr @gep(ptr %p) { + %q = getelementptr i8, ptr %p, i32 3 + ret ptr %q +} +; CHECK-LABEL: @gep +; CHECK: %bit.addr = add i32 %bit.base, 24 +; CHECK-NOT: getelementptr