Ufunc codegen refactor

Big refactor in the code-generation for numpy ufuncs. Note: this is not about vectorize, but about code generated when a numpy ufunc is found in a jitted function.

Basically removed lots of duplicated code. Also isolated the portions of code that handle iteration over operands from the code inner function that is applied for each element.

The iteration portion is now a single function that supports any input arity (although a single output is currently supported). The inner operation is defined by a "kernel", that is just a class that handles that kernel specific.

The idea is preventing details of a given operation to add complex configuration to a common function handling all the ufuncs (look at the old code for generating binary-ufuncs and how division added lots of complexity).

This should ease the addition of support for new ufuncs, specially those that differ in logic quite a bit from the currently supported ones (logical operators come to mind).
will check, it would make things way easier as well.