Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully implement ++ and -- #486

Merged
merged 2 commits into from
Jun 23, 2019
Merged

Fully implement ++ and -- #486

merged 2 commits into from
Jun 23, 2019

Conversation

ajor
Copy link
Member

@ajor ajor commented Mar 19, 2019

Issue #412

@ajor
Copy link
Member Author

ajor commented Mar 19, 2019

Despite the title I've only done ++ for maps so far, but -- and variables will be very similar

@dalehamel

@ajor
Copy link
Member Author

ajor commented Mar 22, 2019

I'm not very pleased with the output of the AST printer, but I'm not sure a better way to represent it.

y = ++x

=
 y
 ++ (pre)
  x

y = x++

=
 y
 ++ (post)
  x

It also makes error messages in the semantic analyser a bit uglier, e.g. $x = "blah"; $x++:
The ++ (post) operator can not be used on expressions of type 'string'

Maybe something like this, but suggestions are welcome:
y = ++x

=
 y
 ++
  x

y = x++

=
 y
 x
  ++

@brendangregg
Copy link
Contributor

Second examples look more intuitive.

@@ -111,9 +111,11 @@ class Binop : public Expression {

class Unop : public Expression {
public:
Unop(int op, Expression *expr) : expr(expr), op(op) { }
Unop(int op, Expression *expr, bool is_post_op = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach of using the unary operator. I had considered something similar but couldn't quite get it figured out

b_.CreateMapUpdateElem(map, key, newval);
b_.CreateLifetimeEnd(key);

if (unop.is_post_op)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is exactly how I was thinking of implementing pre/post 👍

}
break;
}
case bpftrace::Parser::token::MINUSMINUS:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably reuse most of the ++ code, and just change:

 b_.CreateStore(b_.CreateAdd(oldval, b_.getInt64(1)), newval);

To CreateSub. Not sure of the cleanest way to DRY this up but it shouldn't be too hard.

@dalehamel
Copy link
Contributor

I agree the second printing example is more intuitive.

This approach looks good! It's a simpler version of what I had in mind, overloading the unop constructor is a good approach.

@mmarchini
Copy link
Contributor

@ajor did you had a chance to work on this? I'm trying to fix #544 and I might have to the parser for that.

@ajor
Copy link
Member Author

ajor commented Apr 16, 2019

Not yet, but it shouldn't take me long to finish off. Are you saying there'll be conflicts? I don't mind fixing them up in this pull request if you're blocked.

@mmarchini
Copy link
Contributor

Not yet, but it shouldn't take me long to finish off. Are you saying there'll be conflicts? I don't mind fixing them up in this pull request if you're blocked.

I was mistaken, this wasn't needed to fix #544. Also, at a glance it doesn't seem like this PR will conflict with recent changes.

@mmarchini
Copy link
Contributor

I want to fix the two shift/reduce conflicts we have on our parser, but I'll wait until this land since it might involve rearranging some grammar rules.

@mmarchini mmarchini modified the milestone: 0.9.1 May 24, 2019
@mmarchini
Copy link
Contributor

@ajor were you able to work on this lately? Would be nice to include this in the next version (0.9.1).

@ajor
Copy link
Member Author

ajor commented May 24, 2019

Finished off my other two pull requests last weekend, so will get back to this one next

@ajor
Copy link
Member Author

ajor commented May 26, 2019

I've implemented it fully now. The ntop codegen tests had to be changed because they used ++ on a map without first initialising it. I think it's ok that this isn't allowed now, because .count() is what should be used there to get more accurate results (uses PER_CPU maps) and we shouldn't encourage bad practises like this by making them easier to type than the preferred way. If people really want to use ++ on a map to count things then they just have to initialise it to 0 first.

@ajor ajor marked this pull request as ready for review May 26, 2019 21:44
@ajor
Copy link
Member Author

ajor commented Jun 2, 2019

Read through #448 and now I'm thinking we should maybe keep ++ initialising to 0 for consistency. Would have to make it clear in the documentation that count() is better though

@mmarchini
Copy link
Contributor

Or we can change #448 to not allow compound assignment of uninitialized variables and maps.

@mmarchini
Copy link
Contributor

tl;dr: changing how compound assignments work for uninitialized variables doesn't make sense, so we should change this to make it coherent with compound assignments.


diff --git a/src/ast/semantic_analyser.cpp b/src/ast/semantic_analyser.cpp
index aa58df0..ddbfe40 100644
--- a/src/ast/semantic_analyser.cpp
+++ b/src/ast/semantic_analyser.cpp
@@ -855,7 +855,6 @@ void SemanticAnalyser::visit(ExprStatement &expr)
 
 void SemanticAnalyser::visit(AssignMapStatement &assignment)
 {
-  assignment.map->accept(*this);
   assignment.expr->accept(*this);
 
   std::string map_ident = assignment.map->ident;
@@ -877,15 +876,19 @@ void SemanticAnalyser::visit(AssignMapStatement &assignment)
     }
   }
   else {
-    // This map hasn't been seen before
-    map_val_.insert({map_ident, assignment.expr->type});
-    if (map_val_[map_ident].type == Type::integer) {
-      // Store all integer values as 64-bit in maps, so that there will
-      // be space for any integer to be assigned to the map later
-      map_val_[map_ident].size = 8;
+    if (is_final_pass()) {
+      // This map hasn't been seen before
+      map_val_.insert({map_ident, assignment.expr->type});
+      if (map_val_[map_ident].type == Type::integer) {
+        // Store all integer values as 64-bit in maps, so that there will
+        // be space for any integer to be assigned to the map later
+        map_val_[map_ident].size = 8;
+      }
     }
   }
 
+  assignment.map->accept(*this);
+
   if (assignment.expr->type.type == Type::cast) {
     std::string cast_type = assignment.expr->type.cast_type;
     std::string curr_cast_type = map_val_[map_ident].cast_type;

This seems enough to force initialization of maps on compound assignment. But it changes the behavior of maps. For example, the program below is valid today:

# ./src/bpftrace -e 'kprobe:f { @x = @y; @y = 2; }'
Attaching 1 probe...

But it would fail with the patch above:

# ./src/bpftrace -e 'kprobe:f { @x = @y; @y = 2; }'
Undefined map: @y

At the same time, the program below kinda makes sense, but would be invalid with the patch above:

# ./src/bpftrace -e 'kprobe:f { @x = @y; } kprobe:do_nanosleep { @y = 2; }'
Undefined map: @y

But if we change the probe order it works:

# ./src/bpftrace -e 'kprobe:do_nanosleep { @y = 2; } kprobe:f { @x = @y; }'
Undefined map: @y

Which doesn't make sense since we can't guarantee the execution order of two different probes. Either both programs should fail or be valid. If both programs fail, we won't be able to use maps across different probes, so we should keep the behavior as it is today.

As for compound assignments, @a += 1 should behave the same as @a = @a + 1. Having a specific check to see if @a is defined somewhere else would be too cumbersome, so we should probably keep things as is here as well.

As for increment, users would expect @a++ to behave the same way as @a += 1, so we should change it to accept uninitialized maps as well.

Variables are a different case, and no changes should be needed in either PR.

I also think we might want to better explain maps behavior in our reference guide. Today we only have examples there, which makes it look like both maps and variables behave the same, with the only difference being their scope (https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#2---basic-variables).

@mmarchini
Copy link
Contributor

An alternative implementation would be to turn assignments into expressions, and allow for pre/post assignments. Then we could implement using Assing*Expression insead of unop, and would guarantee the same behavior between normal assignments, compound assignments and increment/decrement. This would also allow for C-style syntax like below:

if (($a = [expression]) == [other expression]) {
   ...
}

Not saying that the syntax above is a good idea or that this would be a better implementation, just sharing an alternative that would comply with compound assignment behavior.

@ajor
Copy link
Member Author

ajor commented Jun 11, 2019

Assignments as expressions might be something we should do, but I'll leave it out of this pull request and just get this one working some other way.

EDIT: Changed my mind after thinking about what I'd have to do to implement this :P
I'll look into assignment expressions

@ajor ajor added the do-not-merge Changes are not ready to be merged into master yet label Jun 11, 2019
@ajor
Copy link
Member Author

ajor commented Jun 12, 2019

Done - please review again!

I'd misunderstood what you meant about assignment expressions when I commented yesterday - I've just done it without them now

@ajor ajor removed the do-not-merge Changes are not ready to be merged into master yet label Jun 12, 2019
Copy link
Contributor

@mmarchini mmarchini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Suggested changes on tests are not critical and we can add it later.


TEST(codegen, map_increment_decrement)
{
test("BEGIN { @x = 10; @x++; ++@x; @x--; --@x; }",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also test undefined map here:

Suggested change
test("BEGIN { @x = 10; @x++; ++@x; @x--; --@x; }",
test("BEGIN { @x = 10; @x++; ++@x; @x--; --@x; @y++ }",

@@ -92,3 +92,13 @@ NAME defines work
RUN bpftrace -e "$(echo '#define _UNDERSCORE 314'; echo 'BEGIN { printf("%d\\n", _UNDERSCORE); exit(); }')"
EXPECT 314
TIMEOUT 1

NAME increment/decrement map
RUN bpftrace -e 'BEGIN { @x = 10; printf("%d", @x++); printf(" %d", ++@x); printf(" %d", @x--); printf(" %d\n", --@x); delete(@x); exit(); }'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here:

Suggested change
RUN bpftrace -e 'BEGIN { @x = 10; printf("%d", @x++); printf(" %d", ++@x); printf(" %d", @x--); printf(" %d\n", --@x); delete(@x); exit(); }'
RUN bpftrace -e 'BEGIN { @x = 10; printf("%d", @x++); printf(" %d", ++@x); printf(" %d", @x--); printf(" %d\n", --@x); printf(" %d", @y++); delete(@x); delete(@y); exit(); }'

@ajor
Copy link
Member Author

ajor commented Jun 12, 2019

Unrelated tests are failing probably due to me doing a dodgy rebase - I'll fix them too before merging

@mmarchini
Copy link
Contributor

This should fix the last failing test:

diff --git a/tests/codegen/unroll.cpp b/tests/codegen/unroll.cpp
index 2fcfa87..6b8b27d 100644
--- a/tests/codegen/unroll.cpp
+++ b/tests/codegen/unroll.cpp
@@ -6,7 +6,7 @@ namespace codegen {
 
 TEST(codegen, unroll)
 {
-  test("BEGIN { @i = 0; unroll(5) { @i++ } }",
+  test("BEGIN { @i = 0; unroll(5) { @i += 1 } }",
 
 R"EXPECTED(; Function Attrs: nounwind
 declare i64 @llvm.bpf.pseudo(i64, i64) #0

@ajor
Copy link
Member Author

ajor commented Jun 17, 2019

Ahh that makes more sense! Thanks I'll get that updated

@ajor ajor merged commit ba3c1b0 into master Jun 23, 2019
@ajor ajor deleted the prepost-incdec-expr branch June 23, 2019 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants