-
Notifications
You must be signed in to change notification settings - Fork 20
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
Encountered failed assertion: static_var.h:117 #76
Comments
Seems this assertion is triggered when Please correct me if I've misunderstood something! |
Diagnosed exactly why this is happening. I don't have a solution unfortunately, so I'd still appreciate the help! I'm trying to get BuildIt to play well with Eigen, a standard linalg library for roboticists and graphics people. Long story short, destructor ordering for My question: how do I replace the copy constructor with just a move? Or write it better? This copy is generated during a return statement, and should ideally be elided but doesn't for some reason... Some background on my usecase and Eigen internals follows. I'm trying to generate code from this minimal input code snippet:
Here's the intended codegen'd output:
This is what cscalar looks like:
Here's pseudocode on roughly how arith ops are performed in Eigen (
Here's the order of operations:
What's the best way to make sure |
Hi Radhika, Let's start with why the destructor order is important. The main reason static_var template is required is to keep track of mutations to first stage variables to determine whether two executions can be merged. Every time an operation happens on dyn_var BuildIt takes a snapshot of all alive static_var's and creates a string to record the state. This string is later compared to identify merging opportunities. To keep track of alive static_var's BuildIt pushes the address of the variable to a vector (attached to the context) and pops it later when the static var is destroyed. This simple design allows us to keep track of which static var is which without maintaining a key value pair. This not only makes the storage efficient, it also makes the comparisons of snapshots easier. Now, like I said, we can easily relax this design if necessary by replacing the vector with a map and selectively removing elements from the map. But let us make sure that is necessary. If I understand correctly, in your example, a temporary cscalar is created which is copied into another cscalar. But the temporary is never destroyed. BuildIt aside this most likely happens when rule of (3/5/0)[https://en.cppreference.com/w/cpp/language/rule_of_three] is violated. This might be the case in your example because you have a copy constructor and a default constructor but no destructor. Unless, some of these operators allocate the object on the heap with There might be several ways to approach from here, but let us start from the simplest option -
Wouldn't compile because add_op returns a Scalar (which in this case is a prvalue) and assign_op expects an lvalue as its second parameter. Is this exactly the code you are trying or is this a simplified example. If you can share a working example that repros the static_var assertion I can take a stab at fixing the order of cosntructor/destructors. You don't necessarily have to share a "buildit.so" link, if you have a repo, you can link/share it, I can build and figure it out.
I think we should consider each of the above options in order. Maybe the most useful starting point right now would be to answer 1 and share a working example that compiles -> runs -> crashes on assert. I can then debug and figure out a way to fix the order of constructors/destructors. I hope that makes sense. I am really curious to see how this experiment with Eigen goes. It should definitely be a very useful application for BuildIt. Thanks |
Hi Ajay, no worries, and thanks for your explanation of why destructor order for static_vars matters! Here's the repo I've been working in, please use the radhika-dev branch. Relevant files are: To repro the assertion:
Addressing some of your comments/concerns:
Now that I think about it, in this minimal case,
Yep this is what I'm doing! This is also what is causing the dangling pointer, since this object is never free'd. You might ask why am I returning a cscalar instead of just returning an expression, much like in the barray tutorial language. I do have the alternate version implemented which returns exprs instead (you can compile it by setting Question 2: After some hunting I saw array.h in BuildIt's source, and figured there must be some quirk in handling arrays inside BuildIt. (link to Eigen's internal array allocator). Could you tell me a bit about arrays of BuildIt types? I am quite puzzled about this too... (and I'd use this implementation had the early termination situation not happened)
This is a simplified snippet of what Eigen does (links from Eigen's codebase: Line 2, Line 3, Line 4). Seems the simplified example is missing important details! I can dig up the exact template instantiation and get back to you.
Seeing your explanation, pretty sure this is the problem; I likely got sth wrong with the constructors/destructors for my class. Will try to poke at it; getting sample4 running is a good first step. Thanks a lot for your help! (and for hand-holding me through the basics of C++ lol) |
Could you tell me a bit about when/why do LoopBack exceptions happen in BuildIt, specifically when using BuildIt types in arrays? |
Hey Radhika, I am taking a closer look at the eigen implementation that you have. Let me get back to you about both these points by tomorrow. |
Hi Radhika,
If you plan is to never modify a scalar, I think this would be the best solution going forward. Even if we get static_var to work how you want, I am guessing this implementation would produce really large number of static vars (one per element in the matrix?). static_vars do come with a little overhead because each operation on the dynamic variables have to snapshot all static vars and compare them with other states. This can be fairly expensive for large matrices. One way to ensure you are never actually modifying any cscalars might be to delete the operator= explicitly. So the only way to create new values would be through the copy constructor and other operations, which can always create new is_constant/constant_val instead of modifying it in place.
So this is because of the way static_vars interact with dyn_vars. Let us first understand what a LoopBackException is (your follow up question). You can see that LoopBackException is thrown exactly in one place in BuildIt here. Basically whenever BuildIt creates a new statement in the generated code, it checks if an exact same statement with the same static tag has been created before. If it has been created, it means the execution it going to be exactly the same. Remember static tags are a concatenation of where you are in the program and the state of all the static vars. When this exception is thrown, the execution of the program stops and BuildIt simply inserts a goto (back edge) in the generated code. So now, if a static var that should be tracked is not tracked, it will lead BuildIt to believe that it is safe to go back and it will insert a backedge which essentially becomes a while(1). This is a 100% symptom of missing something that should be static but isn't marked static.
This is generally not a good idea for C++ since you would later have no way to free the returned value. The preferred way is to usually return by value and let the compiler either do ROV or aggressive inlining to optimize it. Anyway, returning *new is not going to be great for performance anyway. What does Eigen usually do in such cases for the usual scalars?
This might be the easiest approach since this way you can also defer the computation to later further optimizing it. The way to define a cast operator is actually the ideal way to convert an expr into a cscalar. You could also define a constructor for cscalar from cscalar_epxr which should have the same effect.
Arrays inside dyn_var like dyn_var<int[4]> or just dyn_var<int[]> should behave just like any other dyn_var. The key idea is that these produce arrays in the generated code. There is a separate dyn_arr which actually creates an array of dynamic variables which can be indexed by first stage values. This doesn't really produce an array but a bunch of dyn_vars and indexing into it picks up the appropriate dyn_var where used. You can look at sample43 which might help understand it better. I have a higher level suggestion here. For Einsum do you really need each cscalar to be a separate dyn_var? Can you not represent the entire matrix as a single dyn_var? Maybe as a 2-d array? dyn_var<int[][]>? Furthermore, do the static_vars is_constant and constant_val need to be tracked for each cscalar? Can they be tracked in an aggregate way for either the entire matrix or parts of it (like say a row?). Are there optimizations that benefit from tracking the constness of each value?
I think in general if you are just always creating new objects of type cscalar instead of modifying them in place, you should be able to use is_constant and constant_val without static. The LoopBackException followed by while(1) should also go away if you make sure you are indeed not modifying the cscalars anywhere (I would start by looking for loops where they might be updated). One easy way like I suggested before might be to delete the operator= to find out where it might have been modified. Happy to help debug this more. This really seems like a cool use case for BuildIt and I curious to see how it works. Finally, if you do need out of order destroyable static_vars, let me know. I can enable/add them under a flag in builder_context. There might be a modest overhead but not much compared to the overhead of tracking static_vars anyway. |
Thanks for your notes on BuildIt's internals and implementation tips! I'll take a stab at implementing your suggestions, will post more if/when I get stuck again!
Some details about our usecase (happy to share more, we're trying to codegen robot-specific kinematics and dynamics math using the robot type as static_var input):
This is exactly what I was looking for! I was somehow expecting a So I assume BuildIt dyn_vars (and dyn_var-like structs such as cscalars) won't play well with Eigen's internal storage array then? (I tried to use a plain I can try to get Eigen to talk to a dyn_arr, and see what happens...
Edit: okay turns out implementing the sparse matmul example was really easy in BuildIt lol |
Hey Radhika,
Okay these details are helpful to understand! So 40x40 isn't that big that it should cause a huge problem. I was worried you are planning to use arrays of sizes in the 100s or thousands or millions. Creating a separate static variable for each might slow everything down to a crawl.
dyn_var array[SIZE] is our design intention. However, the only problem is that the loop that calls the constructor for each element would have to be static_var. That is essentially what dyn_arr does. It allocates space for a dyn_var array[size] but manually loops over and calls constructors on each in a static loop. The empty while loops you got are probably because of the same issue. Because Eigen::Matrix<dyn_var> creates an array and tries to initialize them with a regular loop. You essentially have a variable (the index) in the first stage that is being mutated without being marked static. I think the easiest fix if you want to continue using Eigen::Matrix<dyn_var> would be to write a partial specialization for Eigen::Matrix<dyn_var> that uses dyn_arr instead of array of dyn_var. That way the rest of the code would remain exactly the same. Also, glad that you got sparse matmul working. It should be exactly the same as writing a regular matmul with matrix as static. Infact we also have a "roll" operation in BuildIt that was written to optimize stuff like this. You don't have to use it, but it might make the generated code easier. You can find it under samples/sample29.cpp. Again, not very essential but it rolls up similar statements into a loop and makes arrays to fill up values. Basic mechanical stuff if you want to convert back unrolled loops into loops. Let me know if you would like to hop on a zoom call sometime to debug Eigen. I am really curious about how this go. I might also be able to come to Harvard if you would like to work on it in person. Thanks |
Hey @RadhikaG were you able to get this working by any chance? Would any support from us help with it? I am really curious about how the whole Eigen project goes because it's an interesting usecase for BuildIt. Thanks |
Hi @AjayBrahmakshatriya, thank you for following up! I actually had to pause the Eigen integration work for a little bit to make progress on some other features in our compiler. I will almost certainly have to come back to this soon though, so I will be pinging here again if/when I get stuck! |
I was literally just typing out an email to you about sth related to this lol thank you so much!! Just pulled in your changes, the assertion I was hitting is now gone, but now I know my own code is broken atm lol I'll close this issue for now, will re-open it if I discover a static_var bug later. |
Hi @RadhikaG, Btw, there was a test case failing due to the change (it was failing only on MacOS so I couldn't catch it early). I have fixed it now. Please feel free to pull the latest change. Thanks |
Hi, I got a failed assertion recently with the following error message:
buildit/include/builder/static_var.h:117: builder::static_var<T>::~static_var() [with T = double]: Assertion builder_context::current_builder_context->static_var_tuples.back().ptr == (unsigned char *)&val' failed.
I probably made a mistake somewhere in my code; is there a particular situation in which this assertion is triggered? Would be super helpful to know what to look for when I go bug hunting.
Thank you!
The text was updated successfully, but these errors were encountered: