Skip to content

Commit 22b7ecd

Browse files
ryantimwilsonjordalgo
authored andcommitted
Use global scratch buffers for variables
This commit migrates variables to use global scratch variables if size > on_stack_limit. This will allow users to store large strings/tuples/objects in local variables.
1 parent 7b72595 commit 22b7ecd

18 files changed

+354
-108
lines changed

.github/include/aot_skip.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ aot.call.str_big_for
4343
aot.call.str_big_tuple
4444
# Same problem as #3392
4545
aot.call.tuple_scratch_buf
46+
# Same problem as #3392
47+
aot.call.variable_for_loop_scratch_buf
4648
aot.call.str_truncated
4749
aot.call.str_truncated_custom
4850
aot.call.strftime

src/ast/async_ids.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace ast {
1919
DO(system) \
2020
DO(time) \
2121
DO(tuple) \
22+
DO(variable) \
2223
DO(watchpoint)
2324

2425
#define DEFINE_MEMBER_VAR(id, ...) int _##id = 0;

src/ast/irbuilderbpf.cpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ AllocaInst *IRBuilderBPF::CreateAllocaBPF(const SizedType &stype,
183183
return CreateAllocaBPF(ty, name);
184184
}
185185

186+
void IRBuilderBPF::CreateAllocationInit(const SizedType &stype, Value *alloc)
187+
{
188+
if (needMemcpy(stype)) {
189+
CreateMemsetBPF(alloc, getInt8(0), stype.GetSize());
190+
} else {
191+
CreateStore(ConstantInt::get(GetType(stype), 0), alloc);
192+
}
193+
}
194+
186195
AllocaInst *IRBuilderBPF::CreateAllocaBPFInit(const SizedType &stype,
187196
const std::string &name)
188197
{
@@ -194,11 +203,7 @@ AllocaInst *IRBuilderBPF::CreateAllocaBPFInit(const SizedType &stype,
194203
llvm::Type *ty = GetType(stype);
195204
alloca = CreateAlloca(ty, nullptr, name);
196205
CreateLifetimeStart(alloca);
197-
if (needMemcpy(stype)) {
198-
CreateMemsetBPF(alloca, getInt8(0), stype.GetSize());
199-
} else {
200-
CreateStore(ConstantInt::get(ty, 0), alloca);
201-
}
206+
CreateAllocationInit(stype, alloca);
202207
});
203208
return alloca;
204209
}
@@ -553,6 +558,28 @@ Value *IRBuilderBPF::CreateWriteMapValueAllocation(const SizedType &value_type,
553558
loc);
554559
}
555560

561+
Value *IRBuilderBPF::CreateVariableAllocationInit(const SizedType &value_type,
562+
const std::string &name,
563+
const location &loc)
564+
{
565+
/* Hoist variable declaration and initialization to entry point of
566+
* probe/subprogram. While we technically do not need this as variables
567+
* are properly scoped, it eases debugging and is consistent with previous
568+
* stack-only variable implementation. */
569+
Value *alloc;
570+
hoist([this, &value_type, &name, &loc, &alloc] {
571+
alloc = createAllocation(bpftrace::globalvars::GlobalVar::VARIABLE_BUFFER,
572+
GetType(value_type),
573+
name,
574+
loc,
575+
[](AsyncIds &async_ids) {
576+
return async_ids.variable();
577+
});
578+
CreateAllocationInit(value_type, alloc);
579+
});
580+
return alloc;
581+
}
582+
556583
Value *IRBuilderBPF::createAllocation(
557584
bpftrace::globalvars::GlobalVar globalvar,
558585
llvm::Type *obj_type,

src/ast/irbuilderbpf.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ class IRBuilderBPF : public IRBuilder<> {
175175
Value *CreateWriteMapValueAllocation(const SizedType &value_type,
176176
const std::string &name,
177177
const location &loc);
178+
Value *CreateVariableAllocationInit(const SizedType &value_type,
179+
const std::string &name,
180+
const location &loc);
178181
void CreateCheckSetRecursion(const location &loc, int early_exit_ret);
179182
void CreateUnSetRecursion(const location &loc);
180183
CallInst *CreateHelperCall(libbpf::bpf_func_id func_id,
@@ -314,6 +317,7 @@ class IRBuilderBPF : public IRBuilder<> {
314317
const location &loc,
315318
std::optional<std::function<size_t(AsyncIds &)>>
316319
gen_async_id_cb = std::nullopt);
320+
void CreateAllocationInit(const SizedType &stype, Value *alloc);
317321
Value *createScratchBuffer(globalvars::GlobalVar globalvar,
318322
const location &loc,
319323
size_t key);

src/ast/passes/codegen_llvm.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,7 +2428,8 @@ void CodegenLLVM::visit(AssignMapStatement &assignment)
24282428
}
24292429

24302430
void CodegenLLVM::maybeAllocVariable(const std::string &var_ident,
2431-
const SizedType &var_type)
2431+
const SizedType &var_type,
2432+
const location &loc)
24322433
{
24332434
if (maybeGetVariable(var_ident) != nullptr) {
24342435
// Already been allocated
@@ -2445,9 +2446,9 @@ void CodegenLLVM::maybeAllocVariable(const std::string &var_ident,
24452446
alloca_type = CreatePointer(pointee_type, var_type.GetAS());
24462447
}
24472448

2448-
AllocaInst *val = b_.CreateAllocaBPFInit(alloca_type, var_ident);
2449+
auto val = b_.CreateVariableAllocationInit(alloca_type, var_ident, loc);
24492450
variables_[scope_stack_.back()][var_ident] = VariableLLVM{
2450-
val, val->getAllocatedType()
2451+
val, b_.GetType(alloca_type)
24512452
};
24522453
}
24532454

@@ -2479,7 +2480,7 @@ void CodegenLLVM::visit(AssignVarStatement &assignment)
24792480

24802481
auto scoped_del = accept(assignment.expr);
24812482

2482-
maybeAllocVariable(var.ident, var.type);
2483+
maybeAllocVariable(var.ident, var.type, var.loc);
24832484

24842485
if (var.type.IsArrayTy() || var.type.IsRecordTy()) {
24852486
// For arrays and structs, only the pointer is stored
@@ -2512,7 +2513,7 @@ void CodegenLLVM::visit(VarDeclStatement &decl)
25122513
// unused and has no type
25132514
return;
25142515
}
2515-
maybeAllocVariable(var.ident, var.type);
2516+
maybeAllocVariable(var.ident, var.type, var.loc);
25162517
}
25172518

25182519
void CodegenLLVM::visit(If &if_node)

src/ast/passes/codegen_llvm.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ class CodegenLLVM : public Visitor {
265265
const SizedType &key_type);
266266

267267
void maybeAllocVariable(const std::string &var_ident,
268-
const SizedType &var_type);
268+
const SizedType &var_type,
269+
const location &loc);
269270
VariableLLVM *maybeGetVariable(const std::string &);
270271
VariableLLVM &getVariable(const std::string &);
271272

src/ast/passes/resource_analyser.cpp

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ std::optional<RequiredResources> ResourceAnalyser::analyse()
8686
bpftrace::globalvars::GlobalVar::MAX_CPU_ID);
8787
}
8888

89+
if (resources_.max_variable_size > 0) {
90+
assert(resources_.variable_buffers > 0);
91+
resources_.needed_global_vars.insert(
92+
bpftrace::globalvars::GlobalVar::VARIABLE_BUFFER);
93+
resources_.needed_global_vars.insert(
94+
bpftrace::globalvars::GlobalVar::MAX_CPU_ID);
95+
}
96+
8997
return std::optional{ std::move(resources_) };
9098
}
9199

@@ -114,8 +122,6 @@ void ResourceAnalyser::visit(Call &call)
114122
{
115123
Visitor::visit(call);
116124

117-
const auto on_stack_limit = bpftrace_.config_.get(
118-
ConfigKeyInt::on_stack_limit);
119125
if (call.func == "printf" || call.func == "system" || call.func == "cat" ||
120126
call.func == "debugf") {
121127
// Implicit first field is the 64bit printf ID.
@@ -152,7 +158,7 @@ void ResourceAnalyser::visit(Call &call)
152158
// will use this information to create a percpu array map of large
153159
// enough size for all fmt string calls to use.
154160
const auto tuple_size = static_cast<uint64_t>(tuple->size);
155-
if (tuple_size > on_stack_limit) {
161+
if (exceeds_stack_limit(tuple_size)) {
156162
resources_.max_fmtstring_args_size = std::max(
157163
resources_.max_fmtstring_args_size,
158164
static_cast<uint64_t>(tuple_size));
@@ -227,7 +233,7 @@ void ResourceAnalyser::visit(Call &call)
227233
resources_.non_map_print_args.push_back(arg.type);
228234

229235
const auto fmtstring_args_size = nonmap_headroom + arg.type.GetSize();
230-
if (fmtstring_args_size > on_stack_limit) {
236+
if (exceeds_stack_limit(fmtstring_args_size)) {
231237
resources_.max_fmtstring_args_size = std::max(
232238
resources_.max_fmtstring_args_size, fmtstring_args_size);
233239
}
@@ -237,7 +243,7 @@ void ResourceAnalyser::visit(Call &call)
237243
resources_.non_map_print_args.push_back(map.type);
238244

239245
const auto fmtstring_args_size = nonmap_headroom + map.type.GetSize();
240-
if (fmtstring_args_size > on_stack_limit) {
246+
if (exceeds_stack_limit(fmtstring_args_size)) {
241247
resources_.max_fmtstring_args_size = std::max(
242248
resources_.max_fmtstring_args_size, fmtstring_args_size);
243249
}
@@ -261,7 +267,7 @@ void ResourceAnalyser::visit(Call &call)
261267
} else if (call.func == "delete") {
262268
auto &arg0 = *call.vargs.at(0);
263269
auto &map = static_cast<Map &>(arg0);
264-
if (map.type.GetSize() > on_stack_limit) {
270+
if (exceeds_stack_limit(map.type.GetSize())) {
265271
resources_.max_write_map_value_size = std::max(
266272
resources_.max_write_map_value_size, map.type.GetSize());
267273
}
@@ -279,7 +285,7 @@ void ResourceAnalyser::visit(Call &call)
279285

280286
if (call.func == "str" || call.func == "buf" || call.func == "path") {
281287
const auto max_strlen = bpftrace_.config_.get(ConfigKeyInt::max_strlen);
282-
if (max_strlen > on_stack_limit)
288+
if (exceeds_stack_limit(max_strlen))
283289
resources_.str_buffers++;
284290
}
285291

@@ -296,9 +302,7 @@ void ResourceAnalyser::visit(Map &map)
296302

297303
update_map_info(map);
298304

299-
const auto on_stack_limit = bpftrace_.config_.get(
300-
ConfigKeyInt::on_stack_limit);
301-
if (map.type.GetSize() > on_stack_limit) {
305+
if (exceeds_stack_limit(map.type.GetSize())) {
302306
resources_.read_map_value_buffers++;
303307
resources_.max_read_map_value_size = std::max(
304308
resources_.max_read_map_value_size, map.type.GetSize());
@@ -309,9 +313,7 @@ void ResourceAnalyser::visit(Tuple &tuple)
309313
{
310314
Visitor::visit(tuple);
311315

312-
const auto on_stack_limit = bpftrace_.config_.get(
313-
ConfigKeyInt::on_stack_limit);
314-
if (tuple.type.GetSize() > on_stack_limit) {
316+
if (exceeds_stack_limit(tuple.type.GetSize())) {
315317
resources_.tuple_buffers++;
316318
resources_.max_tuple_size = std::max(resources_.max_tuple_size,
317319
tuple.type.GetSize());
@@ -323,9 +325,7 @@ void ResourceAnalyser::visit(For &f)
323325
Visitor::visit(f);
324326

325327
// Need tuple per for loop to store key and value
326-
const auto on_stack_limit = bpftrace_.config_.get(
327-
ConfigKeyInt::on_stack_limit);
328-
if (f.decl->type.GetSize() > on_stack_limit) {
328+
if (exceeds_stack_limit(f.decl->type.GetSize())) {
329329
resources_.tuple_buffers++;
330330
resources_.max_tuple_size = std::max(resources_.max_tuple_size,
331331
f.decl->type.GetSize());
@@ -360,9 +360,7 @@ void ResourceAnalyser::visit(AssignMapStatement &assignment)
360360
update_map_info(*assignment.map);
361361

362362
if (needAssignMapStatementAllocation(assignment)) {
363-
const auto on_stack_limit = bpftrace_.config_.get(
364-
ConfigKeyInt::on_stack_limit);
365-
if (assignment.map->type.GetSize() > on_stack_limit) {
363+
if (exceeds_stack_limit(assignment.map->type.GetSize())) {
366364
resources_.max_write_map_value_size = std::max(
367365
resources_.max_write_map_value_size, assignment.map->type.GetSize());
368366
}
@@ -379,14 +377,46 @@ void ResourceAnalyser::visit(Ternary &ternary)
379377
// blow it up. So we need a scratch buffer for it.
380378

381379
if (ternary.type.IsStringTy()) {
382-
const auto on_stack_limit = bpftrace_.config_.get(
383-
ConfigKeyInt::on_stack_limit);
384380
const auto max_strlen = bpftrace_.config_.get(ConfigKeyInt::max_strlen);
385-
if (max_strlen > on_stack_limit)
381+
if (exceeds_stack_limit(max_strlen))
386382
resources_.str_buffers++;
387383
}
388384
}
389385

386+
void ResourceAnalyser::update_variable_info(Variable &var)
387+
{
388+
/* Note we don't check if a variable has been declared/assigned before.
389+
* We do this to simplify the code and make it more robust to changes
390+
* in other modules at the expense of memory over-allocation. Otherwise,
391+
* we would need to track scopes like SemanticAnalyser and CodegenLLVM
392+
* and duplicate scope tracking in a third module.
393+
*/
394+
if (exceeds_stack_limit(var.type.GetSize())) {
395+
resources_.variable_buffers++;
396+
resources_.max_variable_size = std::max(resources_.max_variable_size,
397+
var.type.GetSize());
398+
}
399+
}
400+
401+
void ResourceAnalyser::visit(AssignVarStatement &assignment)
402+
{
403+
Visitor::visit(assignment);
404+
405+
update_variable_info(*assignment.var);
406+
}
407+
408+
void ResourceAnalyser::visit(VarDeclStatement &decl)
409+
{
410+
Visitor::visit(decl);
411+
412+
update_variable_info(*decl.var);
413+
}
414+
415+
bool ResourceAnalyser::exceeds_stack_limit(size_t size)
416+
{
417+
return size > bpftrace_.config_.get(ConfigKeyInt::on_stack_limit);
418+
}
419+
390420
bool ResourceAnalyser::uses_usym_table(const std::string &fun)
391421
{
392422
return fun == "usym" || fun == "func" || fun == "ustack";

src/ast/passes/resource_analyser.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,17 @@ class ResourceAnalyser : public Visitor {
3737
void visit(For &f) override;
3838
void visit(Ternary &ternary) override;
3939
void visit(AssignMapStatement &assignment) override;
40+
void visit(AssignVarStatement &assignment) override;
41+
void visit(VarDeclStatement &decl) override;
4042

4143
// Determines whether the given function uses userspace symbol resolution.
4244
// This is used later for loading the symbol table into memory.
4345
bool uses_usym_table(const std::string &fun);
4446

47+
bool exceeds_stack_limit(size_t size);
48+
4549
void update_map_info(Map &map);
50+
void update_variable_info(Variable &var);
4651

4752
RequiredResources resources_;
4853
Node *root_;

src/ast/passes/semantic_analyser.cpp

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3004,22 +3004,6 @@ void SemanticAnalyser::visit(AssignVarStatement &assignment)
30043004
if (storedTy.IsNoneTy())
30053005
LOG(ERROR, assignment.expr->loc, err_)
30063006
<< "Invalid expression for assignment: " << storedTy;
3007-
3008-
// Scratch variables are on the BPF stack, which is only 512 bytes at time
3009-
// of writing, so large values do not fit on there. 200 is a ballpark of
3010-
// what probably won't work.
3011-
auto expr_size = storedTy.GetSize();
3012-
if (expr_size > 200) {
3013-
LOG(ERROR, assignment.loc, err_)
3014-
<< "Value is too big "
3015-
<< "(" << expr_size << " bytes) for the stack. "
3016-
<< "Try reducing its size, storing it in a map, or creating it in "
3017-
"argument position to a helper call.\n\n"
3018-
<< "Examples:\n"
3019-
<< " `$s = str(..);` => `$s = str(.., 32);`\n"
3020-
<< " `$s = str(..);` => `@s = str(..);`\n"
3021-
<< " `$s = str(..); print($s);` => `print(str(..));`\n\n";
3022-
}
30233007
}
30243008
}
30253009

src/globalvars.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ static void update_global_vars_rodata(
142142
case GlobalVar::GET_STR_BUFFER:
143143
case GlobalVar::READ_MAP_VALUE_BUFFER:
144144
case GlobalVar::WRITE_MAP_VALUE_BUFFER:
145+
case GlobalVar::VARIABLE_BUFFER:
145146
break;
146147
}
147148
}
@@ -287,6 +288,12 @@ SizedType get_type(bpftrace::globalvars::GlobalVar global_var,
287288
assert(resources.max_write_map_value_size > 0);
288289
return make_rw_type(
289290
1, CreateArray(resources.max_write_map_value_size, CreateInt8()));
291+
case GlobalVar::VARIABLE_BUFFER:
292+
assert(resources.variable_buffers > 0);
293+
assert(resources.max_variable_size > 0);
294+
return make_rw_type(resources.variable_buffers,
295+
CreateArray(resources.max_variable_size,
296+
CreateInt8()));
290297
}
291298
return {}; // unreachable
292299
}

0 commit comments

Comments
 (0)