glsl: Add frexp signatures and implementation.
authorMatt Turner <mattst88@gmail.com>
Mon, 9 Sep 2013 18:13:20 +0000 (11:13 -0700)
committerMatt Turner <mattst88@gmail.com>
Wed, 18 Sep 2013 00:01:58 +0000 (17:01 -0700)
I initially implemented frexp() as an IR opcode with a lowering pass,
but since it returns a value and has an out-parameter, it would break
assumptions our optimization passes make about ir_expressions being pure
(i.e., having no side effects).

For example, if opt_tree_grafting encounters this code:

uniform float u;
void main()
{
  int exp;
  float f = frexp(u, out exp);
  float g = float(exp)/256.0;
  float h = float(exp) + 1.0;
  gl_FragColor = vec4(f, g, h, g + h);
}

it may try to optimize it to this:

uniform float u;
void main()
{
  int exp;
  float g = float(exp)/256.0;
  float h = float(exp) + 1.0;
  gl_FragColor = vec4(frexp(u, out exp), g, h, g + h);
}

Some hardware has an instruction which performs frexp(), but we would
need some other compiler infrastructure to be able to generate it, such
as an intrinsics system that would allow backends to emit specific code
for particular bits of IR.

Reviewed-by: Paul Berry <stereotype441@gmail.com>
src/glsl/builtin_functions.cpp

index b5be704..31f7489 100644 (file)
@@ -511,6 +511,7 @@ private:
    B1(findMSB)
    B1(fma)
    B2(ldexp)
+   B2(frexp)
 #undef B0
 #undef B1
 #undef B2
@@ -1830,6 +1831,13 @@ builtin_builder::create_builtins()
                 _ldexp(glsl_type::vec3_type,  glsl_type::ivec3_type),
                 _ldexp(glsl_type::vec4_type,  glsl_type::ivec4_type),
                 NULL);
+
+   add_function("frexp",
+                _frexp(glsl_type::float_type, glsl_type::int_type),
+                _frexp(glsl_type::vec2_type,  glsl_type::ivec2_type),
+                _frexp(glsl_type::vec3_type,  glsl_type::ivec3_type),
+                _frexp(glsl_type::vec4_type,  glsl_type::ivec4_type),
+                NULL);
 #undef F
 #undef FI
 #undef FIU
@@ -3528,6 +3536,54 @@ builtin_builder::_ldexp(const glsl_type *x_type, const glsl_type *exp_type)
 {
    return binop(ir_binop_ldexp, gpu_shader5, x_type, x_type, exp_type);
 }
+
+ir_function_signature *
+builtin_builder::_frexp(const glsl_type *x_type, const glsl_type *exp_type)
+{
+   ir_variable *x = in_var(x_type, "x");
+   ir_variable *exponent = out_var(exp_type, "exp");
+   MAKE_SIG(x_type, gpu_shader5, 2, x, exponent);
+
+   const unsigned vec_elem = x_type->vector_elements;
+   const glsl_type *bvec = glsl_type::get_instance(GLSL_TYPE_BOOL, vec_elem, 1);
+   const glsl_type *uvec = glsl_type::get_instance(GLSL_TYPE_UINT, vec_elem, 1);
+
+   /* Single-precision floating-point values are stored as
+    *   1 sign bit;
+    *   8 exponent bits;
+    *   23 mantissa bits.
+    *
+    * An exponent shift of 23 will shift the mantissa out, leaving only the
+    * exponent and sign bit (which itself may be zero, if the absolute value
+    * was taken before the bitcast and shift.
+    */
+   ir_constant *exponent_shift = imm(23);
+   ir_constant *exponent_bias = imm(-126, vec_elem);
+
+   ir_constant *sign_mantissa_mask = imm(0x807fffffu, vec_elem);
+
+   /* Exponent of floating-point values in the range [0.5, 1.0). */
+   ir_constant *exponent_value = imm(0x3f000000u, vec_elem);
+
+   ir_variable *is_not_zero = body.make_temp(bvec, "is_not_zero");
+   body.emit(assign(is_not_zero, nequal(abs(x), imm(0.0f, vec_elem))));
+
+   /* Since abs(x) ensures that the sign bit is zero, we don't need to bitcast
+    * to unsigned integers to ensure that 1 bits aren't shifted in.
+    */
+   body.emit(assign(exponent, rshift(bitcast_f2i(abs(x)), exponent_shift)));
+   body.emit(assign(exponent, add(exponent, csel(is_not_zero, exponent_bias,
+                                                     imm(0, vec_elem)))));
+
+   ir_variable *bits = body.make_temp(uvec, "bits");
+   body.emit(assign(bits, bitcast_f2u(x)));
+   body.emit(assign(bits, bit_and(bits, sign_mantissa_mask)));
+   body.emit(assign(bits, bit_or(bits, csel(is_not_zero, exponent_value,
+                                                imm(0u, vec_elem)))));
+   body.emit(ret(bitcast_u2f(bits)));
+
+   return sig;
+}
 /** @} */
 
 /******************************************************************************/