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

tudasc / TypeART / 25175874405

30 Apr 2026 04:03PM UTC coverage: 89.231% (-1.0%) from 90.246%
25175874405

Pull #188

github

web-flow
Merge 61645e210 into 278119205
Pull Request #188: GPU memory allocation support

210 of 297 new or added lines in 20 files covered. (70.71%)

4 existing lines in 3 files now uncovered.

5071 of 5683 relevant lines covered (89.23%)

33116.84 hits per line

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

94.84
/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/GpuUtil.h"
20
#include "support/Error.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"
1,696✔
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,926✔
58
    : MemOpVisitor(config[config::ConfigStdArgs::stack], config[config::ConfigStdArgs::heap],
9,852✔
59
                   config[config::ConfigStdArgs::gpu]) {
4,926✔
60
}
4,926✔
NEW
61
MemOpVisitor::MemOpVisitor(bool stack, bool heap) : MemOpVisitor(stack, heap, true) {
×
NEW
62
}
×
63

64
MemOpVisitor::MemOpVisitor(bool stack, bool heap, bool gpu)
6,622✔
65
    : collect_allocas(stack), collect_heap(heap), collect_gpu(gpu) {
5,010✔
66
}
3,314✔
67

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

71
  for (auto& [lifetime, alloc] : lifetime_starts) {
607,202✔
72
    auto* data = llvm::find_if(
575,997✔
73
        allocas, [alloc_ = std::ref(alloc)](const AllocaData& alloca_data) { return alloca_data.alloca == alloc_; });
1,274,965✔
74
    if (data != std::end(allocas)) {
575,997✔
75
      data->lifetime_start.insert(lifetime);
20,960✔
76
    }
12,314✔
77
  }
78

79
  for (const auto& alloc : allocas) {
62,860✔
80
    if (alloc.lifetime_start.size() > 1) {
31,655✔
81
      LOG_DEBUG("Lifetime: " << alloc.lifetime_start.size());
82
      LOG_DEBUG(*alloc.alloca);
83
      for (auto* lifetime : alloc.lifetime_start) {
36✔
84
        LOG_DEBUG(*lifetime);
85
      }
86
    }
12✔
87
  }
88
}
31,205✔
89

90
void MemOpVisitor::collectGlobals(Module& module) {
4,926✔
91
  for (auto& g : module.globals()) {
181,200✔
92
    globals.emplace_back(GlobalData{&g});
176,274✔
93
  }
94
}
4,926✔
95

96
void MemOpVisitor::visitCallBase(llvm::CallBase& cb) {
174,683✔
97
  if (!collect_heap) {
174,683✔
98
    return;
61,946✔
99
  }
100
  const auto* called_function = cb.getCalledFunction();
112,737✔
101
  if (!collect_gpu && called_function != nullptr && gpu::is_gpu_function(*called_function)) {
112,737✔
NEW
102
    return;
×
103
  }
104
  const auto isInSet = [&](const auto& fMap) -> std::optional<MemOpKind> {
335,331✔
105
    if (called_function == nullptr) {
222,594✔
106
      // TODO handle calls through, e.g., function pointers? - seems infeasible
107
      // LOG_INFO("Encountered indirect call, skipping.");
108
      return {};
984✔
109
    }
110
    const auto name = called_function->getName().str();
221,610✔
111

112
    const auto res = fMap.find(name);
221,610✔
113
    if (res != fMap.end()) {
221,610✔
114
      return {(*res).second};
5,053✔
115
    }
116
    return {};
216,557✔
117
  };
222,594✔
118

119
  if (auto alloc_val = isInSet(mem_operations.allocs())) {
112,737✔
120
    visitMallocLike(cb, alloc_val.value());
2,880✔
121
  } else if (auto dealloc_val = isInSet(mem_operations.deallocs())) {
112,737✔
122
    visitFreeLike(cb, dealloc_val.value());
2,173✔
123
  }
2,173✔
124
}
174,683✔
125

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

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

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

143
  auto* target_instruction =
276✔
144
      dyn_cast<InstTy>(*llvm::find_if(users, [](llvm::User* use) { return llvm::isa<InstTy>(*use); }));
738✔
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),
207✔
149
                   "Did not expect malloc-like \"{0}\" as store value operand.", *value);
3✔
150
    // }
151
  }
152

153
  if (num_asan_call != 0) {
264✔
154
    const auto* asan_call = dyn_cast<CallBase>(*llvm::find_if(users, [](llvm::User* user) {
144✔
155
      CallSite csite(user);
90✔
156
      if (!(csite.isCall() || csite.isInvoke()) || csite.getCalledFunction() == nullptr) {
90✔
157
        return false;
36✔
158
      }
159
      const auto name = csite.getCalledFunction()->getName();
54✔
160
      return util::starts_with_any_of(name, "__asan");
54✔
161
    }));
90✔
162
    if constexpr (std::is_same_v<InstTy, llvm::StoreInst>) {
163
      RETURN_NONE_IF(target_instruction->getPointerOperand() != asan_call->getArgOperand(0),
54✔
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
  }
54✔
172

173
  return {target_instruction};
264✔
174
}
276✔
175

176
using MallocGeps   = SmallPtrSet<GetElementPtrInst*, 2>;
177
using MallocBcasts = SmallPtrSet<BitCastInst*, 4>;
12,541✔
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) {
1,820✔
214
  auto* slot = store_inst->getPointerOperand();
1,820✔
215

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

222
  for (auto* slot_user : slot->users()) {
10,656✔
223
    // TODO: Ensure that load occurs after store?
224
    if (auto* load_inst = llvm::dyn_cast<llvm::LoadInst>(slot_user)) {
8,848✔
225
      for (auto* load_user : load_inst->users()) {
9,994✔
226
        if (auto* bit_cast = llvm::dyn_cast<llvm::BitCastInst>(load_user)) {
6,806✔
227
          out_bcasts.insert(bit_cast);
24✔
228
        }
24✔
229
      }
230
    }
3,188✔
231
  }
232
}
1,820✔
233

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

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

245
  for (auto* user : call_inst.users()) {
7,595✔
246
    if (auto* bit_cast = llvm::dyn_cast<llvm::BitCastInst>(user)) {
4,726✔
247
      bcasts.insert(bit_cast);
809✔
248
    } else if (auto* gep_inst = llvm::dyn_cast<llvm::GetElementPtrInst>(user)) {
4,726✔
249
      geps.insert(gep_inst);
225✔
250
    } else if (auto* store_inst = llvm::dyn_cast<llvm::StoreInst>(user)) {
3,917✔
251
      collect_casts_from_stack(store_inst, bcasts);
1,820✔
252
    }
1,820✔
253
  }
254

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

258
std::optional<ArrayCookieData> handleUnpaddedArrayCookie(llvm::CallBase& ci, const MallocGeps& geps,
189✔
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!");
57✔
264
  auto cookie_bcast = *bcasts.begin();
57✔
265
  RETURN_NONE_IF(!isi64Ptr(cookie_bcast->getDestTy()), "Found non-i64Ptr bitcast instruction for an array cookie!");
57✔
266

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

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

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

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

288
std::optional<ArrayCookieData> handlePaddedArrayCookie(llvm::CallBase& ci, const MallocGeps& geps, MallocBcasts& bcasts,
18✔
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!");
6✔
294

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

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

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

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

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

323
std::optional<ArrayCookieData> handleArrayCookie(llvm::CallBase& ci, const MallocGeps& geps, MallocBcasts& bcasts,
2,880✔
324
                                                 BitCastInst*& primary_cast) {
325
  if (geps.size() == 1) {
2,880✔
326
    return handleUnpaddedArrayCookie(ci, geps, bcasts, primary_cast);
189✔
327
  } else if (geps.size() == 2) {
2,691✔
328
    return handlePaddedArrayCookie(ci, geps, bcasts, primary_cast);
18✔
329
  } else if (geps.size() > 2) {
2,673✔
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,673✔
341
}
2,880✔
342

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

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

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

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

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

374
void MemOpVisitor::visitAllocaInst(llvm::AllocaInst& ai) {
86,869✔
375
  if (!collect_allocas) {
86,869✔
376
    return;
55,214✔
377
  }
378
  //  LOG_DEBUG("Found alloca " << ai);
379
  Value* arraySizeOperand = ai.getArraySize();
31,655✔
380
  size_t arraySize{0};
31,655✔
381
  bool is_vla{false};
31,655✔
382
  if (auto arraySizeConst = llvm::dyn_cast<ConstantInt>(arraySizeOperand)) {
31,655✔
383
    arraySize = arraySizeConst->getZExtValue();
31,493✔
384
  } else {
31,493✔
385
    is_vla = true;
162✔
386
  }
387

388
  allocas.push_back({&ai, arraySize, is_vla});
63,310✔
389
  //  LOG_DEBUG("Alloca: " << util::dump(ai) << " -> lifetime marker: " << util::dump(lifetimes));
390
}
59,311✔
391

392
void MemOpVisitor::visitIntrinsicInst(llvm::IntrinsicInst& inst) {
179,987✔
393
  if (inst.getIntrinsicID() != Intrinsic::lifetime_start) {
179,987✔
394
    return;
153,509✔
395
  }
396
  AllocaInst* alloca{nullptr};
26,478✔
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);
22,325✔
402
  alloca        = llvm::findAllocaForValue(operand);
22,325✔
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) {
26,478✔
410
    lifetime_starts.emplace_back(&inst, alloca);
26,475✔
411
  }
26,475✔
412
}
179,987✔
413
void MemOpVisitor::clear() {
31,205✔
414
  allocas.clear();
31,205✔
415
  mallocs.clear();
31,205✔
416
  frees.clear();
31,205✔
417
}
31,205✔
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