• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tudasc / TypeART / 25177145272

30 Apr 2026 04:30PM UTC coverage: 84.647% (-5.6%) from 90.246%
25177145272

Pull #188

github

web-flow
Merge 88918912c into 278119205
Pull Request #188: GPU memory allocation support

127 of 259 new or added lines in 19 files covered. (49.03%)

200 existing lines in 18 files now uncovered.

4510 of 5328 relevant lines covered (84.65%)

27776.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.86
/lib/passes/analysis/MemOpVisitor.cpp
1
// TypeART library
2
//
3
// Copyright (c) 2017-2026 TypeART Authors
4
// Distributed under the BSD 3-Clause license.
5
// (See accompanying file LICENSE.txt or copy at
6
// https://opensource.org/licenses/BSD-3-Clause)
7
//
8
// Project home: https://github.com/tudasc/TypeART
9
//
10
// SPDX-License-Identifier: BSD-3-Clause
11
//
12

13
#include "MemOpVisitor.h"
14

15
#include "analysis/MemOpData.h"
16
#include "compat/CallSite.h"
17
#include "configuration/Configuration.h"
18
#include "support/ConfigurationBase.h"
19
#include "support/Error.h"
20
#include "support/GpuUtil.h"
21
#include "support/Logger.h"
22
#include "support/TypeUtil.h"
23
#include "support/Util.h"
24

25
#include "llvm/ADT/STLExtras.h"
26
#include "llvm/ADT/SmallPtrSet.h"
27
#include "llvm/ADT/StringRef.h"
28

29
#include <llvm/IR/Instruction.h>
30
#include <llvm/Support/Error.h>
31
#include <type_traits>
32

33
#if LLVM_VERSION_MAJOR >= 12
34
#include "llvm/Analysis/ValueTracking.h"  // llvm::findAllocaForValue
35
#else
36
#include "llvm/Transforms/Utils/Local.h"  // llvm::findAllocaForValue
37
#endif
38
#include "llvm/IR/Constants.h"
39
#include "llvm/IR/Function.h"
40
#include "llvm/IR/InstrTypes.h"
41
#include "llvm/IR/Instructions.h"
42
#include "llvm/IR/Module.h"
43
#include "llvm/IR/Value.h"
44
#include "llvm/Support/Casting.h"
45
#include "llvm/Support/raw_ostream.h"
46

47
#include <cstddef>
48
#include <optional>
49

50
namespace typeart::analysis {
51

52
using namespace llvm;
53

54
MemOpVisitor::MemOpVisitor() : MemOpVisitor(true, true) {
×
55
}
×
56

57
MemOpVisitor::MemOpVisitor(const config::Configuration& config)
4,042✔
58
    : MemOpVisitor(config[config::ConfigStdArgs::stack], config[config::ConfigStdArgs::heap],
8,084✔
59
                   config[config::ConfigStdArgs::gpu]) {
4,042✔
60
}
4,042✔
NEW
61
MemOpVisitor::MemOpVisitor(bool stack, bool heap) : MemOpVisitor(stack, heap, true) {
×
NEW
62
}
×
63

64
MemOpVisitor::MemOpVisitor(bool stack, bool heap, bool gpu)
4,042✔
65
    : collect_allocas(stack), collect_heap(heap), collect_gpu(gpu) {
1,618✔
66
}
1,618✔
67

68
void MemOpVisitor::collect(llvm::Function& function) {
24,929✔
69
  visit(function);
24,929✔
70

71
  for (auto& [lifetime, alloc] : lifetime_starts) {
481,210✔
72
    auto* data = llvm::find_if(
456,281✔
73
        allocas, [alloc_ = std::ref(alloc)](const AllocaData& alloca_data) { return alloca_data.alloca == alloc_; });
956,878✔
74
    if (data != std::end(allocas)) {
456,281✔
75
      data->lifetime_start.insert(lifetime);
14,708✔
76
    }
9,188✔
77
  }
78

79
  for (const auto& alloc : allocas) {
48,838✔
80
    if (alloc.lifetime_start.size() > 1) {
23,909✔
81
      LOG_DEBUG("Lifetime: " << alloc.lifetime_start.size());
82
      LOG_DEBUG(*alloc.alloca);
83
      for (auto* lifetime : alloc.lifetime_start) {
45✔
84
        LOG_DEBUG(*lifetime);
85
      }
86
    }
15✔
87
  }
88
}
24,929✔
89

90
void MemOpVisitor::collectGlobals(Module& module) {
4,042✔
91
  for (auto& g : module.globals()) {
160,393✔
92
    globals.emplace_back(GlobalData{&g});
156,351✔
93
  }
94
}
4,042✔
95

96
void MemOpVisitor::visitCallBase(llvm::CallBase& cb) {
143,528✔
97
  if (!collect_heap) {
143,528✔
98
    return;
51,464✔
99
  }
100
  const auto* called_function = cb.getCalledFunction();
92,064✔
101
  if (!collect_gpu && called_function != nullptr && gpu::is_gpu_function(*called_function)) {
92,064✔
NEW
102
    return;
×
103
  }
104
  const auto isInSet = [&](const auto& fMap) -> std::optional<MemOpKind> {
273,767✔
105
    if (called_function == nullptr) {
181,703✔
106
      // TODO handle calls through, e.g., function pointers? - seems infeasible
107
      // LOG_INFO("Encountered indirect call, skipping.");
108
      return {};
440✔
109
    }
110
    const auto name = called_function->getName().str();
181,263✔
111

112
    const auto res = fMap.find(name);
181,263✔
113
    if (res != fMap.end()) {
181,263✔
114
      return {(*res).second};
4,260✔
115
    }
116
    return {};
177,003✔
117
  };
181,703✔
118

119
  if (auto alloc_val = isInSet(mem_operations.allocs())) {
92,064✔
120
    visitMallocLike(cb, alloc_val.value());
2,425✔
121
  } else if (auto dealloc_val = isInSet(mem_operations.deallocs())) {
92,064✔
122
    visitFreeLike(cb, dealloc_val.value());
1,835✔
123
  }
1,835✔
124
}
143,528✔
125

126
template <class InstTy>
127
std::optional<InstTy*> getSingleUserAs(llvm::Instruction* value) {
180✔
128
  auto users            = value->users();
180✔
129
  const auto num_stores = llvm::count_if(users, [](llvm::User* use) { return llvm::isa<InstTy>(*use); });
570✔
130
  RETURN_NONE_IF((num_stores == 0), "Expected a single store on call \"{0}\". It has no users!", *value);
180✔
131

132
  const auto num_asan_call = llvm::count_if(users, [](llvm::User* user) {
570✔
133
    CallSite csite(user);
390✔
134
    if (!(csite.isCall() || csite.isInvoke()) || csite.getCalledFunction() == nullptr) {
390✔
135
      return false;
345✔
136
    }
137
    const auto name = csite.getCalledFunction()->getName();
45✔
138
    return util::starts_with_any_of(name, "__asan");
45✔
139
  });
390✔
140

141
  RETURN_NONE_IF(num_asan_call > 1, "Expected one ASAN call for array cookie.");
180✔
142

143
  auto* target_instruction =
180✔
144
      dyn_cast<InstTy>(*llvm::find_if(users, [](llvm::User* use) { return llvm::isa<InstTy>(*use); }));
570✔
145

146
  if constexpr (std::is_same_v<InstTy, llvm::StoreInst>) {
147
    // if (llvm::isa<CallBase>(value)) {
148
    RETURN_NONE_IF((target_instruction->getValueOperand() == value),
180✔
149
                   "Did not expect malloc-like \"{0}\" as store value operand.", *value);
3✔
150
    // }
151
  }
152

153
  if (num_asan_call != 0) {
165✔
154
    const auto* asan_call = dyn_cast<CallBase>(*llvm::find_if(users, [](llvm::User* user) {
135✔
155
      CallSite csite(user);
90✔
156
      if (!(csite.isCall() || csite.isInvoke()) || csite.getCalledFunction() == nullptr) {
90✔
157
        return false;
45✔
158
      }
159
      const auto name = csite.getCalledFunction()->getName();
45✔
160
      return util::starts_with_any_of(name, "__asan");
45✔
161
    }));
90✔
162
    if constexpr (std::is_same_v<InstTy, llvm::StoreInst>) {
163
      RETURN_NONE_IF(target_instruction->getPointerOperand() != asan_call->getArgOperand(0),
45✔
164
                     "Expected a single user on value \"{0}\" but found multiple potential candidates!", *value);
165
    } else {
166
      if constexpr (std::is_same_v<InstTy, llvm::BitCastInst>) {
167
        RETURN_NONE_IF(target_instruction != asan_call->getArgOperand(0),
168
                       "Expected a single user on value \"{0}\" but found multiple potential candidates!", *value);
169
      }
170
    }
171
  }
45✔
172

173
  return {target_instruction};
165✔
174
}
180✔
175

176
using MallocGeps   = SmallPtrSet<GetElementPtrInst*, 2>;
177
using MallocBcasts = SmallPtrSet<BitCastInst*, 4>;
178

179
// std::pair<MallocGeps, MallocBcasts> collectRelevantMallocUsers(llvm::CallBase& ci) {
180
//   auto geps   = MallocGeps{};
181
//   auto bcasts = MallocBcasts{};
182
//   for (auto user : ci.users()) {
183
//     // Simple case: Pointer is immediately casted
184
//     if (auto inst = dyn_cast<BitCastInst>(user)) {
185
//       bcasts.insert(inst);
186
//     }
187
//     // Pointer is first stored, then loaded and subsequently casted
188
//     if (auto storeInst = dyn_cast<StoreInst>(user)) {
189
//       auto storeAddr = storeInst->getPointerOperand();
190
//       if (!(storeAddr == nullptr || llvm::isa<llvm::ConstantPointerNull>(storeAddr))) {
191
//         for (auto storeUser : storeAddr->users()) {  // TODO: Ensure that load occurs after store?
192
//           if (auto loadInst = dyn_cast<LoadInst>(storeUser)) {
193
//             for (auto loadUser : loadInst->users()) {
194
//               if (auto bcastInst = dyn_cast<BitCastInst>(loadUser)) {
195
//                 // LOG_MSG(*bcastInst)
196
//                 bcasts.insert(bcastInst);
197
//               }
198
//             }
199
//           }
200
//         }
201
//       } else {
202
//         LOG_DEBUG("Null, must skip")
203
//       }
204
//     }
205
//     // GEP indicates that an array cookie is added to the allocation. (Fixes #13)
206
//     if (auto gep = dyn_cast<GetElementPtrInst>(user)) {
207
//       geps.insert(gep);
208
//     }
209
//   }
210
//   return {geps, bcasts};
211
// }
212

213
void collect_casts_from_stack(llvm::StoreInst* store_inst, MallocBcasts& out_bcasts) {
2,125✔
214
  auto* slot = store_inst->getPointerOperand();
2,125✔
215

216
  // Guard: Skip invalid or null storage locations
217
  if (llvm::isa<llvm::ConstantPointerNull>(slot)) {
2,125✔
218
    LOG_DEBUG("Skipping null storage");
219
    return;
15✔
220
  }
221

222
  for (auto* slot_user : slot->users()) {
12,525✔
223
    // TODO: Ensure that load occurs after store?
224
    if (auto* load_inst = llvm::dyn_cast<llvm::LoadInst>(slot_user)) {
10,415✔
225
      for (auto* load_user : load_inst->users()) {
11,930✔
226
        if (auto* bit_cast = llvm::dyn_cast<llvm::BitCastInst>(load_user)) {
8,140✔
UNCOV
227
          out_bcasts.insert(bit_cast);
×
UNCOV
228
        }
×
229
      }
230
    }
3,790✔
231
  }
232
}
2,125✔
233

234
std::pair<MallocGeps, MallocBcasts> collectRelevantMallocUsers(llvm::CallBase& call_inst, MemOpKind kind) {
2,425✔
235
  auto geps   = MallocGeps{};
2,425✔
236
  auto bcasts = MallocBcasts{};
2,425✔
237

238
  if (is_kind(kind, MemOpKind::GpuMallocLike)) {
2,425✔
NEW
239
    if (auto bitcast = gpu::bitcast_for(call_inst, kind); bitcast.has_value()) {
×
NEW
240
      bcasts.insert(*bitcast);
×
NEW
241
    }
×
NEW
242
    return {geps, bcasts};
×
243
  }
244

245
  for (auto* user : call_inst.users()) {
6,950✔
246
    if (auto* bit_cast = llvm::dyn_cast<llvm::BitCastInst>(user)) {
4,525✔
247
      bcasts.insert(bit_cast);
15✔
248
    } else if (auto* gep_inst = llvm::dyn_cast<llvm::GetElementPtrInst>(user)) {
4,525✔
249
      geps.insert(gep_inst);
195✔
250
    } else if (auto* store_inst = llvm::dyn_cast<llvm::StoreInst>(user)) {
4,510✔
251
      collect_casts_from_stack(store_inst, bcasts);
2,125✔
252
    }
2,125✔
253
  }
254

255
  return {geps, bcasts};
2,425✔
256
}
2,425✔
257

258
std::optional<ArrayCookieData> handleUnpaddedArrayCookie(llvm::CallBase& ci, const MallocGeps& geps,
165✔
259
                                                         MallocBcasts& bcasts, BitCastInst*& primary_cast) {
260
  using namespace util::type;
261
#if LLVM_VERSION_MAJOR < 15
262
  // We expect only the bitcast to size_t for the array cookie store.
263
  RETURN_NONE_IF(bcasts.size() != 1, "Couldn't identify bitcast instruction of an unpadded array cookie!");
264
  auto cookie_bcast = *bcasts.begin();
265
  RETURN_NONE_IF(!isi64Ptr(cookie_bcast->getDestTy()), "Found non-i64Ptr bitcast instruction for an array cookie!");
266

267
  auto cookie_store = getSingleUserAs<StoreInst>(cookie_bcast);
268
  RETURN_ON_NONE(cookie_store);
269

270
  auto array_gep = *geps.begin();
271
  RETURN_NONE_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
272

273
  auto array_bcast = getSingleUserAs<BitCastInst>(array_gep);
274
  RETURN_ON_NONE(array_bcast);
275

276
  bcasts.insert(*array_bcast);
277
  primary_cast = *array_bcast;
278
#else
279
  auto cookie_store = getSingleUserAs<StoreInst>(&ci);
165✔
280
  RETURN_ON_NONE(cookie_store);
165✔
281
  // RETURN_NONE_IF(cookie_store.get()->getValueOperand() == &ci, "Cookie store has CallBase as value operand.")
282
  auto array_gep = *geps.begin();
150✔
283
  RETURN_NONE_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
150✔
284
#endif
285
  return {ArrayCookieData{*cookie_store, array_gep}};
150✔
286
}
165✔
287

288
std::optional<ArrayCookieData> handlePaddedArrayCookie(llvm::CallBase& ci, const MallocGeps& geps, MallocBcasts& bcasts,
15✔
289
                                                       BitCastInst*& primary_cast) {
290
  using namespace util::type;
291
#if LLVM_VERSION_MAJOR < 15
292
  // We expect bitcasts only after the GEP instructions in this case.
293
  RETURN_NONE_IF(!bcasts.empty(), "Found unrelated bitcast instructions on a padded array cookie!");
294

295
  auto gep_it     = geps.begin();
296
  auto array_gep  = *gep_it++;
297
  auto cookie_gep = *gep_it++;
298

299
  auto cookie_bcast = getSingleUserAs<BitCastInst>(cookie_gep);
300
  RETURN_ON_NONE(cookie_bcast);
301
  RETURN_NONE_IF(!isi64Ptr((*cookie_bcast)->getDestTy()), "Found non-i64Ptr bitcast instruction for an array cookie!");
302

303
  auto cookie_store = getSingleUserAs<StoreInst>(*cookie_bcast);
304
  RETURN_ON_NONE(cookie_store);
305
  RETURN_NONE_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
306

307
  auto array_bcast = getSingleUserAs<BitCastInst>(array_gep);
308
  RETURN_ON_NONE(array_bcast);
309

310
  bcasts.insert(*array_bcast);
311
  primary_cast = *array_bcast;
312
#else
313
  auto gep_it       = geps.begin();
15✔
314
  auto array_gep    = *gep_it++;
15✔
315
  auto cookie_gep   = *gep_it++;
15✔
316
  auto cookie_store = getSingleUserAs<StoreInst>(cookie_gep);
15✔
317
  RETURN_ON_NONE(cookie_store);
15✔
318
  RETURN_NONE_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
15✔
319
#endif
320
  return {ArrayCookieData{*cookie_store, array_gep}};
15✔
321
}
15✔
322

323
std::optional<ArrayCookieData> handleArrayCookie(llvm::CallBase& ci, const MallocGeps& geps, MallocBcasts& bcasts,
2,425✔
324
                                                 BitCastInst*& primary_cast) {
325
  if (geps.size() == 1) {
2,425✔
326
    return handleUnpaddedArrayCookie(ci, geps, bcasts, primary_cast);
165✔
327
  } else if (geps.size() == 2) {
2,260✔
328
    return handlePaddedArrayCookie(ci, geps, bcasts, primary_cast);
15✔
329
  } else if (geps.size() > 2) {
2,245✔
330
    // Found a case where the address of an allocation is used more than two
331
    // times as an argument to a GEP instruction. This is unexpected as at most
332
    // two GEPs, for calculating the offsets of an array cookie itself and the
333
    // array pointer, are expected.
334
    auto exit_on_error = llvm::ExitOnError{"Array Cookie Detection failed!"};
×
335
    auto err           = "Expected at most two GEP instructions!";
×
336
    LOG_FATAL(err);
×
337
    exit_on_error({error::make_string_error(err)});
×
338
    return {};
×
339
  }
×
340
  return {};
2,245✔
341
}
2,425✔
342

343
void MemOpVisitor::visitMallocLike(llvm::CallBase& ci, MemOpKind k) {
2,425✔
344
  auto [geps, bcasts] = collectRelevantMallocUsers(ci, k);
5,344✔
345
  auto primary_cast   = bcasts.empty() ? nullptr : *bcasts.begin();
2,425✔
346
  auto array_cookie   = handleArrayCookie(ci, geps, bcasts, primary_cast);
3,880✔
347
  if (primary_cast == nullptr) {
2,425✔
348
    LOG_DEBUG("Primary bitcast null: " << ci)
349
  }
2,410✔
350
  mallocs.push_back(MallocData{&ci, array_cookie, primary_cast, bcasts, k, isa<InvokeInst>(ci)});
3,880✔
351
}
2,425✔
352

353
void MemOpVisitor::visitFreeLike(llvm::CallBase& ci, MemOpKind k) {
1,835✔
354
  //  LOG_DEBUG(ci.getCalledFunction()->getName());
355
  MemOpKind kind = k;
1,835✔
356

357
  // FIXME is that superfluous?
358
  if (auto f = ci.getCalledFunction()) {
1,835✔
359
    auto dkind = mem_operations.deallocKind(f->getName());
1,835✔
360
    if (dkind) {
1,835✔
361
      kind = dkind.value();
1,835✔
362
    }
1,835✔
363
  }
1,835✔
364

365
  auto gep              = dyn_cast<GetElementPtrInst>(ci.getArgOperand(0));
1,835✔
366
  auto array_cookie_gep = gep != nullptr ? std::optional<llvm::GetElementPtrInst*>{gep} : std::nullopt;
1,835✔
367
  frees.emplace_back(FreeData{&ci, array_cookie_gep, kind, isa<InvokeInst>(ci)});
1,835✔
368
}
1,835✔
369

370
// void MemOpVisitor::visitIntrinsicInst(llvm::IntrinsicInst& ii) {
371
//
372
//}
373

374
void MemOpVisitor::visitAllocaInst(llvm::AllocaInst& ai) {
68,919✔
375
  if (!collect_allocas) {
68,919✔
376
    return;
45,010✔
377
  }
378
  //  LOG_DEBUG("Found alloca " << ai);
379
  Value* arraySizeOperand = ai.getArraySize();
23,909✔
380
  size_t arraySize{0};
23,909✔
381
  bool is_vla{false};
23,909✔
382
  if (auto arraySizeConst = llvm::dyn_cast<ConstantInt>(arraySizeOperand)) {
23,909✔
383
    arraySize = arraySizeConst->getZExtValue();
23,774✔
384
  } else {
23,774✔
385
    is_vla = true;
135✔
386
  }
387

388
  allocas.push_back({&ai, arraySize, is_vla});
47,818✔
389
  //  LOG_DEBUG("Alloca: " << util::dump(ai) << " -> lifetime marker: " << util::dump(lifetimes));
390
}
27,564✔
391

392
void MemOpVisitor::visitIntrinsicInst(llvm::IntrinsicInst& inst) {
113,919✔
393
  if (inst.getIntrinsicID() != Intrinsic::lifetime_start) {
113,919✔
394
    return;
93,136✔
395
  }
396
  AllocaInst* alloca{nullptr};
20,783✔
397
#if LLVM_VERSION_MAJOR > 21
398
  auto* operand = inst.getArgOperand(0);
4,153✔
399
  alloca        = llvm::findAllocaForValue(operand);
4,153✔
400
#elif LLVM_VERSION_MAJOR >= 12
401
  auto* operand = inst.getOperand(1);
16,630✔
402
  alloca        = llvm::findAllocaForValue(operand);
16,630✔
403
#else
404
  auto* operand = inst.getOperand(1);
405
  DenseMap<Value*, AllocaInst*> alloca_for_value;
406
  alloca = llvm::findAllocaForValue(operand, alloca_for_value);
407
#endif
408

409
  if (alloca != nullptr) {
20,783✔
410
    lifetime_starts.emplace_back(&inst, alloca);
20,783✔
411
  }
20,783✔
412
}
113,919✔
413
void MemOpVisitor::clear() {
24,929✔
414
  allocas.clear();
24,929✔
415
  mallocs.clear();
24,929✔
416
  frees.clear();
24,929✔
417
}
24,929✔
418

419
}  // namespace typeart::analysis
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc