spirv: Add vtn_fail and vtn_assert helpers
authorJason Ekstrand <jason.ekstrand@intel.com>
Wed, 16 Aug 2017 23:15:23 +0000 (16:15 -0700)
committerJason Ekstrand <jason.ekstrand@intel.com>
Mon, 4 Dec 2017 17:21:09 +0000 (09:21 -0800)
These helpers are much nicer than just using assert because they don't
kill your process.  Instead, it longjmps back to spirv_to_nir(), cleans
up all the temporary memory, and nicely returns NULL.  While crashing is
completely OK in the Vulkan world, it's not considered to be quite so
nice in GL.  This should help us to make SPIR-V parsing much more
robust.  The one downside here is that vtn_assert is not compiled out in
release builds like assert() is so it isn't free.

Reviewed-by: Tapani Pälli <tapani.palli@intel.com>
Reviewed-by: Ian Romanick <idr@freedesktop.org>
src/compiler/spirv/spirv_to_nir.c
src/compiler/spirv/vtn_private.h

index 676153d..e26775b 100644 (file)
@@ -106,6 +106,20 @@ _vtn_warn(struct vtn_builder *b, const char *file, unsigned line,
    va_end(args);
 }
 
+void
+_vtn_fail(struct vtn_builder *b, const char *file, unsigned line,
+          const char *fmt, ...)
+{
+   va_list args;
+
+   va_start(args, fmt);
+   vtn_log_err(b, NIR_SPIRV_DEBUG_LEVEL_ERROR, "SPIR-V parsing FAILED:\n",
+               file, line, fmt, args);
+   va_end(args);
+
+   longjmp(b->fail_jump, 1);
+}
+
 struct spec_constant_value {
    bool is_double;
    union {
@@ -3418,6 +3432,12 @@ spirv_to_nir(const uint32_t *words, size_t word_count,
    b->entry_point_name = entry_point_name;
    b->options = options;
 
+   /* See also _vtn_fail() */
+   if (setjmp(b->fail_jump)) {
+      ralloc_free(b);
+      return NULL;
+   }
+
    const uint32_t *word_end = words + word_count;
 
    /* Handle the SPIR-V header (first 4 dwords)  */
index cac4d45..d07f6a2 100644 (file)
@@ -28,6 +28,8 @@
 #ifndef _VTN_PRIVATE_H_
 #define _VTN_PRIVATE_H_
 
+#include <setjmp.h>
+
 #include "nir/nir.h"
 #include "nir/nir_builder.h"
 #include "util/u_dynarray.h"
@@ -49,6 +51,48 @@ void _vtn_warn(struct vtn_builder *b, const char *file, unsigned line,
                const char *fmt, ...) PRINTFLIKE(4, 5);
 #define vtn_warn(...) _vtn_warn(b, __FILE__, __LINE__, __VA_ARGS__)
 
+/** Fail SPIR-V parsing
+ *
+ * This function logs an error and then bails out of the shader compile using
+ * longjmp.  This being safe relies on two things:
+ *
+ *  1) We must guarantee that setjmp is called after allocating the builder
+ *     and setting up b->debug (so that logging works) but before before any
+ *     errors have a chance to occur.
+ *
+ *  2) While doing the SPIR-V -> NIR conversion, we need to be careful to
+ *     ensure that all heap allocations happen through ralloc and are parented
+ *     to the builder.  This way they will get properly cleaned up on error.
+ *
+ *  3) We must ensure that _vtn_fail is never called while a mutex lock or a
+ *     reference to any other resource is held with the exception of ralloc
+ *     objects which are parented to the builder.
+ *
+ * So long as these two things continue to hold, we can easily longjmp back to
+ * spirv_to_nir(), clean up the builder, and return NULL.
+ */
+void _vtn_fail(struct vtn_builder *b, const char *file, unsigned line,
+               const char *fmt, ...) NORETURN PRINTFLIKE(4, 5);
+#define vtn_fail(...) _vtn_fail(b, __FILE__, __LINE__, __VA_ARGS__)
+
+/** Fail if the given expression evaluates to true */
+#define vtn_fail_if(expr, ...) \
+   do { \
+      if (unlikely(expr)) \
+         vtn_fail(__VA_ARGS__); \
+   } while (0)
+
+/** Assert that a condition is true and, if it isn't, vtn_fail
+ *
+ * This macro is transitional only and should not be used in new code.  Use
+ * vtn_fail_if and provide a real message instead.
+ */
+#define vtn_assert(expr) \
+   do { \
+      if (!likely(expr)) \
+         vtn_fail("%s", #expr); \
+   } while (0)
+
 enum vtn_value_type {
    vtn_value_type_invalid = 0,
    vtn_value_type_undef,
@@ -478,6 +522,9 @@ struct vtn_decoration {
 struct vtn_builder {
    nir_builder nb;
 
+   /* Used by vtn_fail to jump back to the beginning of SPIR-V compilation */
+   jmp_buf fail_jump;
+
    const uint32_t *spirv;
 
    nir_shader *shader;