-
Notifications
You must be signed in to change notification settings - Fork 671
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
iree_status_t/Status reworking #265
Comments
This makes it possible to change the type of iree_status_t in future changes. Progress on issue #265. PiperOrigin-RevId: 287847316
This makes it possible to change the type of iree_status_t with most code not caring about what its bits are. Progress on issue #265. PiperOrigin-RevId: 287847316
This makes it possible to change the type of iree_status_t with most code not caring about what its bits are. Progress on issue #265. PiperOrigin-RevId: 287847316
This makes it possible to change the type of iree_status_t with most code not caring about what its bits are. Progress on issue #265. PiperOrigin-RevId: 287847316
This makes it possible to change the type of iree_status_t with most code not caring about what its bits are. Progress on issue #265. PiperOrigin-RevId: 287847316
This makes it possible to change the type of iree_status_t with most code not caring about what its bits are. Progress on issue #265. PiperOrigin-RevId: 287847316
This makes it possible to change the type of iree_status_t with most code not caring about what its bits are. Progress on issue #265. PiperOrigin-RevId: 288026341
See example workaround in #657 that should be supported. |
Spent some time code golfing, got something that seems usable as a foundation: Based on a storage struct created when the non-status is created and then a linked list of payloads. The final handler of the status (likely whatever binding code is calling into the C API) is responsible for freeing the status memory in the case of a failure. This lets us write things like this to append messages to status values:
I was able to make it so that all error handling code is moved out of the core path which will be great for I$ usage. Example of foo() above:
So the cost in success cases is no worse than a bool. If you aren't annotating things, which is the common case, then simple checks will fold; for example above Even then, the annotations are pretty good. I force no inlining so that the code generated is always just the trampoline to the creation function. For example, annotating an unformatted message:
So, in ticking the boxes:
The manual cleanup is annoying, but required for calling code written in C. My next step is to see if I can make a C++ wrapper that is easy to use. If I can, then I may switch iree::Status to the wrapper and use this C impl as the base everywhere. That way we can preserve payloads in non-string forms to allow the bindings on the other side of the C boundary to extract the payloads (and say, propagate errors in more native forms). |
Ohhh! Very nice! |
Very nice! This is going to be awesome! |
C macros and compiler builtin functions FTW! :) |
Progress on #265. # Conflicts: # iree/vm/bytecode_dispatch.c
Progress on #265. # Conflicts: # iree/vm/bytecode_dispatch.c
Progress on #265. # Conflicts: # iree/vm/bytecode_dispatch.c
…oss the VM (#2849) This fixes #265 by implementing the described iree_status_t behavior and changing Status/StatusBuilder/StatusOr to wrap it. By doing this I was able to remove quite a bit of gunk that was required to move between the Google-style C++ Status and the API (such as ToApiStatus/FromApiStatus). By being able to integrate iree_status_t into the C++ types I was also able to unify all of the macros - so IREE_CHECK_OK and IREE_RETURN_IF_ERROR can take either iree_status_t or Status/etc. Because the behavior is now different we can no longer use the iree/base/google/ versions and as such I've renamed all macros such that there are no collisions with the non-IREE versions. Now, IREE_CHECK_OK/IREE_RETURN_IF_ERROR when used in C code can take optional printf-style arguments to add annotations to results, for example: `IREE_RETURN_IF_ERROR(OtherFunc(...), "with a value: %d", 5);` When used in C++ the macros still accept printf-style arguments but can also use the ostream style: `IREE_RETURN_IF_ERROR(OtherFunc(...)) << "with a value: " << 5;`. Though it's supported one shouldn't mix them! The most interesting changes in this branch are in iree/base/api.h + iree/base/api.c and the iree/base/internal/status* files. Other changes are mechanical cleanups required to avoid name conflicts, enable C compilation of core parts (or code to be used both in C and C++), and removing abseil deps from files that are C compatible. A lot of focus was put on keeping the status usage - even if source location, messages, and stack traces are attached - from increasing codesize or overhead when on the happy success path. https://gcc.godbolt.org/z/xGsPc3 For example, this chain of calls and checks:  Ends up as the minimal possible code (while still having any kind of success checks):  The error handling cases are moved out of the way and (hopefully) kept out of cache. The handlers are able to hit the tail call optimization happy path and keeps the whole function code size smaller: 
I've been thinking about how to get good Status-like features (particularly chained messages and stack traces) through the C API. Our big limitation is that we have no RAII for the return values and as such storing anything inside of them (ala Status payloads) mandates that callers do something to them.
I've looked at a few other ML/mathish frameworks to see what they do (as that's what people coming into IREE may be expecting):
Since our HAL is Vulkan(/D3D12/Metal)-like, following DirectML/MPS seems fine. Vulkan uses a combo of per-call error codes on a small set of functions (mainly those that allocate or submit/wait) and otherwise leaves everything to VK_EXT_debug_report per-instance handlers.
Some design points that fall out of this:
This gives us a few major cases to cover:
I think we can cover both of these without needing a diagnostics reporter like callback system. As users can't legally use invocation results without asking for completion we have a good point to return any async errors that accumulated. This avoids the need for thread-local/errno style errors and to more closely correlate errors across binding boundaries (as we always have them on the stack).
If this was pure C++ we'd just use Status and be done with it. Unfortunately we can't easily do that without RAII, and Status itself doesn't really have some of the things we'd want to do; for example, it'd be nice to include both host and VM stack traces in statuses.
So given that a per-call Status-like thing can handle all our cases the real question comes down to what kind of information do we want to attach and how do we attach it without making it painful to use.
The approaches I'm debating between are:
I'm leaning towards caller-deletion because the common cases look a lot more like traditional Status behavior and avoid a lot of boilerplate. For example, propagating status back to callers:
If in C++ we can use if-scoping to make the cleanup clearer:
We can use the must-use-result tag to make errors of omission easier to catch (wrap in an IREE_CHECK_OK and continue, or properly handle). ASAN should pretty clearly show leaks in other cases.
To keep the generated code smaller my thought is to have iree_status_t be a uintptr_t with OK being == 0. This should make the common error-free case no more expensive than if a bool/int error was returned and all checks can be non-zero/zero checks instead of specific equality. There's also no need to dereference the status to perform flow control decisions.
As an optimization my thought is to use the lower 4 or 5 bits of the uintptr_t to hold the Status-compatible error code. This only requires that we alloc real status payloads as 32-byte aligned (not a big deal given the frequency) but for payload-free statuses we can entirely avoid allocation and dereferencing; the status is just as heavy as an int. So iree_status_is_aborted() and other helper functions are just masking off the code bits and comparing with the value in a register, meaning that if we are returning things like deadline-exceeded (which can happen in tight fence completion query loops) or aborted flags we have zero performance hit. Only when we want to add payloads (stack traces, messages, etc) would we really allocate memory for the status.
It also makes it possible to - in super tiny builds - strip all custom payloads and map iree_status_t to uint8_t (or whatever) without any change in code required.
For moving between languages/layers we can have the payload alias other internal payloads. For example, we can have a Status* stored in the payload to allow full-fidelity status propagation from C++ through the C API and up to callers.
Implementation plan is:
@stellaraccident SG? any thoughts from a bindings-perspective?
The text was updated successfully, but these errors were encountered: