glsl: Implement constant expr evaluation for bit-shift ops
[mesa.git] / src / glsl / ir_constant_expression.cpp
index e68a7b1a2a206743f1ed15fcccf66da267d7901c..b250a164a1a31f441e3d266e74853ae30118d8d1 100644 (file)
  */
 
 #include <math.h>
-#include "main/macros.h"
+#include "main/core.h" /* for MAX2, MIN2, CLAMP */
 #include "ir.h"
 #include "ir_visitor.h"
 #include "glsl_types.h"
 
+static float
+dot(ir_constant *op0, ir_constant *op1)
+{
+   assert(op0->type->is_float() && op1->type->is_float());
+
+   float result = 0;
+   for (unsigned c = 0; c < op0->type->components(); c++)
+      result += op0->value.f[c] * op1->value.f[c];
+
+   return result;
+}
+
 ir_constant *
 ir_expression::constant_expression_value()
 {
@@ -77,9 +89,9 @@ ir_expression::constant_expression_value()
    if (op[0]->type->is_array()) {
       assert(op[1] != NULL && op[1]->type->is_array());
       switch (this->operation) {
-      case ir_binop_equal:
+      case ir_binop_all_equal:
         return new(ctx) ir_constant(op[0]->has_value(op[1]));
-      case ir_binop_nequal:
+      case ir_binop_any_nequal:
         return new(ctx) ir_constant(!op[0]->has_value(op[1]));
       default:
         break;
@@ -88,6 +100,21 @@ ir_expression::constant_expression_value()
    }
 
    switch (this->operation) {
+   case ir_unop_bit_not:
+       switch (op[0]->type->base_type) {
+       case GLSL_TYPE_INT:
+           for (unsigned c = 0; c < components; c++)
+               data.i[c] = ~ op[0]->value.i[c];
+           break;
+       case GLSL_TYPE_UINT:
+           for (unsigned c = 0; c < components; c++)
+               data.u[c] = ~ op[0]->value.u[c];
+           break;
+       default:
+           assert(0);
+       }
+       break;
+
    case ir_unop_logic_not:
       assert(op[0]->type->base_type == GLSL_TYPE_BOOL);
       for (unsigned c = 0; c < op[0]->type->components(); c++)
@@ -137,6 +164,15 @@ ir_expression::constant_expression_value()
       }
       break;
 
+   case ir_unop_any:
+      assert(op[0]->type->is_boolean());
+      data.b[0] = false;
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        if (op[0]->value.b[c])
+           data.b[0] = true;
+      }
+      break;
+
    case ir_unop_trunc:
       assert(op[0]->type->base_type == GLSL_TYPE_FLOAT);
       for (unsigned c = 0; c < op[0]->type->components(); c++) {
@@ -326,25 +362,9 @@ ir_expression::constant_expression_value()
       break;
 
    case ir_binop_dot:
-      assert(op[0]->type->is_vector() && op[1]->type->is_vector());
-      data.f[0] = 0;
-      for (unsigned c = 0; c < op[0]->type->components(); c++) {
-        switch (op[0]->type->base_type) {
-        case GLSL_TYPE_UINT:
-           data.u[0] += op[0]->value.u[c] * op[1]->value.u[c];
-           break;
-        case GLSL_TYPE_INT:
-           data.i[0] += op[0]->value.i[c] * op[1]->value.i[c];
-           break;
-        case GLSL_TYPE_FLOAT:
-           data.f[0] += op[0]->value.f[c] * op[1]->value.f[c];
-           break;
-        default:
-           assert(0);
-        }
-      }
-
+      data.f[0] = dot(op[0], op[1]);
       break;
+
    case ir_binop_min:
       assert(op[0]->type == op[1]->type || op0_scalar || op1_scalar);
       for (unsigned c = 0, c0 = 0, c1 = 0;
@@ -617,14 +637,97 @@ ir_expression::constant_expression_value()
         assert(0);
       }
       break;
-
    case ir_binop_equal:
-      data.b[0] = op[0]->has_value(op[1]);
+      assert(op[0]->type == op[1]->type);
+      for (unsigned c = 0; c < components; c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] == op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] == op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] == op[1]->value.f[c];
+           break;
+        default:
+           assert(0);
+        }
+      }
       break;
    case ir_binop_nequal:
+      assert(op[0]->type != op[1]->type);
+      for (unsigned c = 0; c < components; c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] != op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] != op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] != op[1]->value.f[c];
+           break;
+        default:
+           assert(0);
+        }
+      }
+      break;
+   case ir_binop_all_equal:
+      data.b[0] = op[0]->has_value(op[1]);
+      break;
+   case ir_binop_any_nequal:
       data.b[0] = !op[0]->has_value(op[1]);
       break;
 
+   case ir_binop_lshift:
+      for (unsigned c = 0, c0 = 0, c1 = 0;
+           c < components;
+           c0 += c0_inc, c1 += c1_inc, c++) {
+
+          if (op[0]->type->base_type == GLSL_TYPE_INT &&
+              op[1]->type->base_type == GLSL_TYPE_INT) {
+              data.i[c] = op[0]->value.i[c0] << op[1]->value.i[c1];
+
+          } else if (op[0]->type->base_type == GLSL_TYPE_INT &&
+                     op[1]->type->base_type == GLSL_TYPE_UINT) {
+              data.i[c] = op[0]->value.i[c0] << op[1]->value.u[c1];
+
+          } else if (op[0]->type->base_type == GLSL_TYPE_UINT &&
+                     op[1]->type->base_type == GLSL_TYPE_INT) {
+              data.u[c] = op[0]->value.u[c0] << op[1]->value.i[c1];
+
+          } else if (op[0]->type->base_type == GLSL_TYPE_UINT &&
+                     op[1]->type->base_type == GLSL_TYPE_UINT) {
+              data.u[c] = op[0]->value.u[c0] << op[1]->value.u[c1];
+          }
+      }
+      break;
+
+   case ir_binop_rshift:
+       for (unsigned c = 0, c0 = 0, c1 = 0;
+            c < components;
+            c0 += c0_inc, c1 += c1_inc, c++) {
+
+           if (op[0]->type->base_type == GLSL_TYPE_INT &&
+               op[1]->type->base_type == GLSL_TYPE_INT) {
+               data.i[c] = op[0]->value.i[c0] >> op[1]->value.i[c1];
+
+           } else if (op[0]->type->base_type == GLSL_TYPE_INT &&
+                      op[1]->type->base_type == GLSL_TYPE_UINT) {
+               data.i[c] = op[0]->value.i[c0] >> op[1]->value.u[c1];
+
+           } else if (op[0]->type->base_type == GLSL_TYPE_UINT &&
+                      op[1]->type->base_type == GLSL_TYPE_INT) {
+               data.u[c] = op[0]->value.u[c0] >> op[1]->value.i[c1];
+
+           } else if (op[0]->type->base_type == GLSL_TYPE_UINT &&
+                      op[1]->type->base_type == GLSL_TYPE_UINT) {
+               data.u[c] = op[0]->value.u[c0] >> op[1]->value.u[c1];
+           }
+       }
+       break;
+
    default:
       /* FINISHME: Should handle all expression types. */
       return NULL;
@@ -648,7 +751,7 @@ ir_swizzle::constant_expression_value()
    ir_constant *v = this->val->constant_expression_value();
 
    if (v != NULL) {
-      ir_constant_data data;
+      ir_constant_data data = { { 0 } };
 
       const unsigned swiz_idx[4] = {
         this->mask.x, this->mask.y, this->mask.z, this->mask.w
@@ -678,18 +781,27 @@ ir_dereference_variable::constant_expression_value()
    if (!var)
       return NULL;
 
-   return var->constant_value ? var->constant_value->clone(NULL) : NULL;
+   /* The constant_value of a uniform variable is its initializer,
+    * not the lifetime constant value of the uniform.
+    */
+   if (var->mode == ir_var_uniform)
+      return NULL;
+
+   if (!var->constant_value)
+      return NULL;
+
+   return var->constant_value->clone(talloc_parent(var), NULL);
 }
 
 
 ir_constant *
 ir_dereference_array::constant_expression_value()
 {
-   void *ctx = talloc_parent(this);
    ir_constant *array = this->array->constant_expression_value();
    ir_constant *idx = this->array_index->constant_expression_value();
 
    if ((array != NULL) && (idx != NULL)) {
+      void *ctx = talloc_parent(this);
       if (array->type->is_matrix()) {
         /* Array access of a matrix results in a vector.
          */
@@ -702,7 +814,7 @@ ir_dereference_array::constant_expression_value()
          */
         const unsigned mat_idx = column * column_type->vector_elements;
 
-        ir_constant_data data;
+        ir_constant_data data = { { 0 } };
 
         switch (column_type->base_type) {
         case GLSL_TYPE_UINT:
@@ -730,7 +842,7 @@ ir_dereference_array::constant_expression_value()
         return new(ctx) ir_constant(array, component);
       } else {
         const unsigned index = idx->value.u[0];
-        return array->get_array_element(index)->clone(NULL);
+        return array->get_array_element(index)->clone(ctx, NULL);
       }
    }
    return NULL;
@@ -771,7 +883,7 @@ ir_call::constant_expression_value()
     * "Function calls to user-defined functions (non-built-in functions)
     *  cannot be used to form constant expressions."
     */
-   if (!this->callee->is_built_in)
+   if (!this->callee->is_builtin)
       return NULL;
 
    unsigned num_parameters = 0;
@@ -840,7 +952,30 @@ ir_call::constant_expression_value()
    } else if (strcmp(callee, "ceil") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_ceil, type, op[0], NULL);
    } else if (strcmp(callee, "clamp") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(num_parameters == 3);
+      unsigned c1_inc = op[1]->type->is_scalar() ? 0 : 1;
+      unsigned c2_inc = op[2]->type->is_scalar() ? 0 : 1;
+      for (unsigned c = 0, c1 = 0, c2 = 0;
+          c < op[0]->type->components();
+          c1 += c1_inc, c2 += c2_inc, c++) {
+
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.u[c] = CLAMP(op[0]->value.u[c], op[1]->value.u[c1],
+                             op[2]->value.u[c2]);
+           break;
+        case GLSL_TYPE_INT:
+           data.i[c] = CLAMP(op[0]->value.i[c], op[1]->value.i[c1],
+                             op[2]->value.i[c2]);
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.f[c] = CLAMP(op[0]->value.f[c], op[1]->value.f[c1],
+                             op[2]->value.f[c2]);
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "cos") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_cos, type, op[0], NULL);
    } else if (strcmp(callee, "cosh") == 0) {
@@ -854,17 +989,44 @@ ir_call::constant_expression_value()
       for (unsigned c = 0; c < op[0]->type->components(); c++)
         data.f[c] = 180.0/M_PI * op[0]->value.f[c];
    } else if (strcmp(callee, "distance") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_float() && op[1]->type->is_float());
+      float length_squared = 0.0;
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        float t = op[0]->value.f[c] - op[1]->value.f[c];
+        length_squared += t * t;
+      }
+      return new(mem_ctx) ir_constant(sqrtf(length_squared));
    } else if (strcmp(callee, "dot") == 0) {
-      expr = new(mem_ctx) ir_expression(ir_binop_dot, type, op[0], op[1]);
+      return new(mem_ctx) ir_constant(dot(op[0], op[1]));
    } else if (strcmp(callee, "equal") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] == op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] == op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] == op[1]->value.f[c];
+           break;
+        case GLSL_TYPE_BOOL:
+           data.b[c] = op[0]->value.b[c] == op[1]->value.b[c];
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "exp") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_exp, type, op[0], NULL);
    } else if (strcmp(callee, "exp2") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_exp2, type, op[0], NULL);
    } else if (strcmp(callee, "faceforward") == 0) {
-      return NULL; /* FINISHME: implement this */
+      if (dot(op[2], op[1]) < 0)
+        return op[0];
+      for (unsigned c = 0; c < op[0]->type->components(); c++)
+        data.f[c] = -op[0]->value.f[c];
    } else if (strcmp(callee, "floor") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_floor, type, op[0], NULL);
    } else if (strcmp(callee, "fract") == 0) {
@@ -872,39 +1034,145 @@ ir_call::constant_expression_value()
    } else if (strcmp(callee, "fwidth") == 0) {
       return ir_constant::zero(mem_ctx, this->type);
    } else if (strcmp(callee, "greaterThan") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] > op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] > op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] > op[1]->value.f[c];
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "greaterThanEqual") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] >= op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] >= op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] >= op[1]->value.f[c];
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "inversesqrt") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_rsq, type, op[0], NULL);
    } else if (strcmp(callee, "length") == 0) {
-      return NULL; /* FINISHME: implement this */
+      return new(mem_ctx) ir_constant(sqrtf(dot(op[0], op[0])));
    } else if (strcmp(callee, "lessThan") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] < op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] < op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] < op[1]->value.f[c];
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "lessThanEqual") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] <= op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] <= op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] <= op[1]->value.f[c];
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "log") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_log, type, op[0], NULL);
    } else if (strcmp(callee, "log2") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_log2, type, op[0], NULL);
    } else if (strcmp(callee, "matrixCompMult") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_float() && op[1]->type->is_float());
+      for (unsigned c = 0; c < op[0]->type->components(); c++)
+        data.f[c] = op[0]->value.f[c] * op[1]->value.f[c];
    } else if (strcmp(callee, "max") == 0) {
       expr = new(mem_ctx) ir_expression(ir_binop_max, type, op[0], op[1]);
    } else if (strcmp(callee, "min") == 0) {
       expr = new(mem_ctx) ir_expression(ir_binop_min, type, op[0], op[1]);
    } else if (strcmp(callee, "mix") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_float() && op[1]->type->is_float());
+      if (op[2]->type->is_float()) {
+        unsigned c2_inc = op[2]->type->is_scalar() ? 0 : 1;
+        unsigned components = op[0]->type->components();
+        for (unsigned c = 0, c2 = 0; c < components; c2 += c2_inc, c++) {
+           data.f[c] = op[0]->value.f[c] * (1 - op[2]->value.f[c2]) +
+                       op[1]->value.f[c] * op[2]->value.f[c2];
+        }
+      } else {
+        assert(op[2]->type->is_boolean());
+        for (unsigned c = 0; c < op[0]->type->components(); c++)
+           data.f[c] = op[op[2]->value.b[c] ? 1 : 0]->value.f[c];
+      }
    } else if (strcmp(callee, "mod") == 0) {
       expr = new(mem_ctx) ir_expression(ir_binop_mod, type, op[0], op[1]);
    } else if (strcmp(callee, "normalize") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_float());
+      float length = sqrtf(dot(op[0], op[0]));
+
+      if (length == 0)
+        return ir_constant::zero(mem_ctx, this->type);
+
+      for (unsigned c = 0; c < op[0]->type->components(); c++)
+        data.f[c] = op[0]->value.f[c] / length;
    } else if (strcmp(callee, "not") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_logic_not, type, op[0], NULL);
    } else if (strcmp(callee, "notEqual") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
+      for (unsigned c = 0; c < op[0]->type->components(); c++) {
+        switch (op[0]->type->base_type) {
+        case GLSL_TYPE_UINT:
+           data.b[c] = op[0]->value.u[c] != op[1]->value.u[c];
+           break;
+        case GLSL_TYPE_INT:
+           data.b[c] = op[0]->value.i[c] != op[1]->value.i[c];
+           break;
+        case GLSL_TYPE_FLOAT:
+           data.b[c] = op[0]->value.f[c] != op[1]->value.f[c];
+           break;
+        case GLSL_TYPE_BOOL:
+           data.b[c] = op[0]->value.b[c] != op[1]->value.b[c];
+           break;
+        default:
+           assert(!"Should not get here.");
+        }
+      }
    } else if (strcmp(callee, "outerProduct") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_vector() && op[1]->type->is_vector());
+      const unsigned m = op[0]->type->vector_elements;
+      const unsigned n = op[1]->type->vector_elements;
+      for (unsigned j = 0; j < n; j++) {
+        for (unsigned i = 0; i < m; i++) {
+           data.f[i+m*j] = op[0]->value.f[i] * op[1]->value.f[j];
+        }
+      }
    } else if (strcmp(callee, "pow") == 0) {
       expr = new(mem_ctx) ir_expression(ir_binop_pow, type, op[0], op[1]);
    } else if (strcmp(callee, "radians") == 0) {
@@ -912,9 +1180,22 @@ ir_call::constant_expression_value()
       for (unsigned c = 0; c < op[0]->type->components(); c++)
         data.f[c] = M_PI/180.0 * op[0]->value.f[c];
    } else if (strcmp(callee, "reflect") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_float());
+      float dot_NI = dot(op[1], op[0]);
+      for (unsigned c = 0; c < op[0]->type->components(); c++)
+        data.f[c] = op[0]->value.f[c] - 2 * dot_NI * op[1]->value.f[c];
    } else if (strcmp(callee, "refract") == 0) {
-      return NULL; /* FINISHME: implement this */
+      const float eta = op[2]->value.f[0];
+      const float dot_NI = dot(op[1], op[0]);
+      const float k = 1.0 - eta * eta * (1.0 - dot_NI * dot_NI);
+      if (k < 0.0) {
+        return ir_constant::zero(mem_ctx, this->type);
+      } else {
+        for (unsigned c = 0; c < type->components(); c++) {
+           data.f[c] = eta * op[0]->value.f[c] - (eta * dot_NI + sqrtf(k))
+                           * op[1]->value.f[c];
+        }
+      }
    } else if (strcmp(callee, "sign") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_sign, type, op[0], NULL);
    } else if (strcmp(callee, "sin") == 0) {
@@ -924,11 +1205,29 @@ ir_call::constant_expression_value()
       for (unsigned c = 0; c < op[0]->type->components(); c++)
         data.f[c] = sinhf(op[0]->value.f[c]);
    } else if (strcmp(callee, "smoothstep") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(num_parameters == 3);
+      assert(op[1]->type == op[0]->type);
+      unsigned edge_inc = op[0]->type->is_scalar() ? 0 : 1;
+      for (unsigned c = 0, e = 0; c < type->components(); e += edge_inc, c++) {
+        const float edge0 = op[0]->value.f[e];
+        const float edge1 = op[1]->value.f[e];
+        if (edge0 == edge1) {
+           data.f[c] = 0.0; /* Avoid a crash - results are undefined anyway */
+        } else {
+           const float numerator = op[2]->value.f[c] - edge0;
+           const float denominator = edge1 - edge0;
+           const float t = CLAMP(numerator/denominator, 0, 1);
+           data.f[c] = t * t * (3 - 2 * t);
+        }
+      }
    } else if (strcmp(callee, "sqrt") == 0) {
       expr = new(mem_ctx) ir_expression(ir_unop_sqrt, type, op[0], NULL);
    } else if (strcmp(callee, "step") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_float() && op[1]->type->is_float());
+      /* op[0] (edge) may be either a scalar or a vector */
+      const unsigned c0_inc = op[0]->type->is_scalar() ? 0 : 1;
+      for (unsigned c = 0, c0 = 0; c < type->components(); c0 += c0_inc, c++)
+        data.f[c] = (op[1]->value.f[c] < op[0]->value.f[c0]) ? 0.0 : 1.0;
    } else if (strcmp(callee, "tan") == 0) {
       assert(op[0]->type->is_float());
       for (unsigned c = 0; c < op[0]->type->components(); c++)
@@ -938,7 +1237,14 @@ ir_call::constant_expression_value()
       for (unsigned c = 0; c < op[0]->type->components(); c++)
         data.f[c] = tanhf(op[0]->value.f[c]);
    } else if (strcmp(callee, "transpose") == 0) {
-      return NULL; /* FINISHME: implement this */
+      assert(op[0]->type->is_matrix());
+      const unsigned n = op[0]->type->vector_elements;
+      const unsigned m = op[0]->type->matrix_columns;
+      for (unsigned j = 0; j < m; j++) {
+        for (unsigned i = 0; i < n; i++) {
+           data.f[m*i+j] += op[0]->value.f[i+n*j];
+        }
+      }
    } else {
       /* Unsupported builtin - some are not allowed in constant expressions. */
       return NULL;