-
Notifications
You must be signed in to change notification settings - Fork 7
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
meta_framegap.lua deopt crash #1571
Comments
Now that is a very interesting error: most obviously, it shouldn't ever be possible to reference a guard that doesn't exist, but the most likely way this sort of bug would occur is that we pick up the wrong guard. The fact that we hit a guard that doesn't exist will make fixing this much easier. Does the bug happen deterministically? If so, this is a wonderful shrinkray candidate! |
It's very deterministic, so I have high hopes of reducing it and tracking down the cause. |
More info on this crash. It is 100% deterministic with There are no side-traces involved. There are two traces executed, call them T1 and T2. T1 has 83 deopt exits. T2 has only 11. T1 is executed once at the beginning, then only T2 is executed after that (many times). Here is a reduced test case: A = setmetatable({}, {
__add = function(A, B)
if B > 200 then
for B = 1, B do
end
end
end,
})
function C(A, B)
do
return A + B
end
end
for B = 1, 300 do
C(A, B)
end Putting prints in
Looking at the jit-asm confirms 56 is well out of bounds, but there is another compiled trace for which that'd be a valid index (i.e. T2). This made me question whether the thread's idea of the "currently running trace". Is it using a guardindex for T2 when running T2? Putting more prints around and pumping up the logging I can see something else fishy. A common pattern in the output is:
That all makes sense to me. But when we crash, we deviate from this pattern:
After A further reduced input is: A = setmetatable({}, {
__add = function(A, B)
if B > 00 then
for B = 0, B do
end
end
end,
})
function C(A, B)
do
return A + B
end
end
for B = 0, 200 do
C(A, B)
end This one more often that not gives a different error:
(It must also give the original error from before, or it wouldn't have passed the interestingness test. The tries 500 times to get the Diff between those two inputs: --- meta_framegap.lua Fri Jan 31 15:45:00 2025
+++ meta_framegap_werid.lua Fri Jan 31 15:56:38 2025
@@ -1,7 +1,7 @@
A = setmetatable({}, {
__add = function(A, B)
- if B > 200 then
- for B = 1, B do
+ if B > 00 then
+ for B = 0, B do
end
end
end,
@@ -11,6 +11,6 @@
return A + B
end
end
-for B = 1, 300 do
+for B = 0, 200 do
C(A, B)
end |
Here's what's happening: A trace, T1 is compiled and executed, which sets the
We then deopt from Question 1) Should we allow compiling traces from within Question 2) If we do, and our feeling is we do, then how do we make sure that the running trace is always up to date. A simple fix would be to have a vector and push to it when executing a trace, and popping when deopting. We probably also want to set |
Small test case for this: #include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yk.h>
#include <yk_testing.h>
void f(YkMT *mt, int who, YkLocation *loc1, YkLocation *loc2, int i) {
fprintf(stderr, "enter\n");
while (i > 0) {
yk_mt_control_point(mt, loc1);
if (who) {
if (i == 1) {
fprintf(stderr, "a\n");
}
if (i == 2) {
fprintf(stderr, "b\n");
}
if (i == 3) {
fprintf(stderr, "c\n");
}
}
fprintf(stderr, "%d\n", i);
i -= 1;
if (loc2 != NULL) {
f(mt, 0, loc2, NULL, i);
}
}
fprintf(stderr, "exit\n");
}
int main(int argc, char **argv) {
YkMT *mt = yk_mt_new(NULL);
yk_mt_hot_threshold_set(mt, 0);
YkLocation loc1 = yk_location_new();
YkLocation loc2 = yk_location_new();
int i = 6;
NOOPT_VAL(loc1);
NOOPT_VAL(loc2);
NOOPT_VAL(i);
f(mt, 1, &loc1, &loc2, i);
yk_location_drop(loc1);
yk_location_drop(loc2);
yk_mt_shutdown(mt);
return (EXIT_SUCCESS);
} |
Previously we -- well, this one is entirely my fault, so "I"! -- tracked per-thread meta-tracing state as a single `MTThreadState` that we updates as necessary. This doesn't work when we have nested execution, tracing and the like, as the bug in ykjit#1571 highlighted. This commit moves us to a stack of `MTThreadState`s. The basic idea is that the stack always has at least one element: `Interpreting`. As we go through other states, we push / pop as appropriate. The test below (from Edd and Lukas) fails on `master` but is fixed by this commit. The implementation is a bit more awkward than one might hope as naive implementations either: 1. Spread lots of knowledge about the stack around the code. That's a disaster waiting to happen. 2. Run into borrow checker problems. This commit gets around this in two phases: 1. We pass closures to `MTThread` which can peek at the current `MTThreadState` (which isn't `Copy`!). 2. Those closures return the "things" we need to update outside that context. This is a bit awkward, and perhaps there's a better API, but this one is at least safe. Co-authored-by: Edd Barrett <[email protected]> Co-authored-by: Lukas Diekmann <[email protected]>
Previously we -- well, this one is entirely my fault, so "I"! -- tracked per-thread meta-tracing state as a single `MTThreadState` that we updates as necessary. This doesn't work when we have nested execution, tracing and the like, as the bug in ykjit#1571 highlighted. This commit moves us to a stack of `MTThreadState`s. The basic idea is that the stack always has at least one element: `Interpreting`. As we go through other states, we push / pop as appropriate. The test below (from Edd and Lukas) fails on `master` but is fixed by this commit. The implementation is a bit more awkward than one might hope as naive implementations either: 1. Spread lots of knowledge about the stack around the code. That's a disaster waiting to happen. 2. Run into borrow checker problems. This commit gets around this in two phases: 1. We pass closures to `MTThread` which can peek at the current `MTThreadState` (which isn't `Copy`!). 2. Those closures return the "things" we need to update outside that context. This is a bit awkward, and perhaps there's a better API, but this one is at least safe. Co-authored-by: Edd Barrett <[email protected]> Co-authored-by: Lukas Diekmann <[email protected]>
With today's yklua (e99ff2) and yk (3dc428) an OpenResty test causes a deopt crash:
To get the test file, you can use the branch from this PR
(Once it's merged, you can just use
main
)The text was updated successfully, but these errors were encountered: