function signatures( dtypes1, dtypes2, dtypes3, options ) {
	var cache;
	var casts;
	var opts;
	var tmp;
	var out;
	var dt1;
	var dt2;
	var dt3;
	var t1;
	var t2;
	var t3;
	var t4;
	var M;
	var N;
	var i;
	var j;
	var k;

	if ( arguments.length > 3 ) {
		opts = options;
	} else {
		opts = {};
	}
	// Resolve the list of provided data types to data type strings:
	dt1 = resolve( dtypes1 );
	if ( dt1 instanceof Error ) {
		throw dt1;
	}
	if ( dtypes2 === dtypes1 ) { // don't do work if we don't need to
		dt2 = dt1;
	} else {
		dt2 = resolve( dtypes2 );
		if ( dt2 instanceof Error ) {
			throw dt2;
		}
	}
	if ( dtypes3 === dtypes1 ) { // don't do work if we don't need to
		dt3 = dt1;
	} else if ( dtypes3 === dtypes2 ) {
		dt3 = dt2;
	} else {
		dt3 = resolve( dtypes3 );
		if ( dt3 instanceof Error ) {
			throw dt3;
		}
	}
	// Sort the list of return dtypes:
	dt3.sort();

	// Initialize a cache for storing the safe casts for promoted dtypes:
	cache = {};

	// Generate the list of signatures...
	M = dt1.length;
	N = dt2.length;
	out = [];
	for ( i = 0; i < M; i++ ) {
		t1 = dt1[ i ];
		for ( j = 0; j < N; j++ ) {
			t2 = dt2[ j ];

			// Resolve the promoted dtype for the current dtype pair:
			t3 = promotionRules( t1, t2 );

			// Check whether the dtype pair promotes...
			if ( t3 === -1 || t3 === null ) {
				// The dtype pair does not promote:
				continue;
			}
			// Check whether the promoted dtype is in our list of output dtypes...
			if ( contains( dt3, t3 ) ) {
				out.push( t1, t2, t3 );
			}
			// Retrieve the allowed casts for the promoted dtype:
			casts = cache[ t3 ];

			// If a list of allowed casts is not in the cache, we need to resolve them...
			if ( casts === void 0 ) {
				// Resolve the list of safe casts for the promoted dtype:
				casts = safeCasts( t3 );

				// Remove safe casts which are not among the supported output dtypes:
				casts = intersection( dt3, casts.sort() );

				// Store the list of safe casts in the cache:
				cache[ t3 ] = casts;
			}
			// Generate signatures for allowed casts...
			for ( k = 0; k < casts.length; k++ ) {
				t4 = casts[ k ];
				if ( t4 !== t3 ) {
					out.push( t1, t2, t4 );
				}
			}
		}
	}
	if ( opts.enums ) {
		tmp = [];
		for ( i = 0; i < out.length; i++ ) {
			tmp.push( resolveEnum( out[ i ] ) );
		}
		out = tmp;
	}
	return out;
}
Generates a list of binary interface signatures from strided array data types.

## Notes

-   The function returns a strided array having a stride length of `3` (i.e., every `3` elements define a binary interface signature).
-   For each signature (i.e., set of three consecutive non-overlapping strided array elements), the first two elements are the input data types and the third element is the return data type.
-   All signatures follow type promotion rules.

@param {Array} dtypes1 - list of supported data types for the first argument
@param {Array} dtypes2 - list of supported data types for the second argument
@param {Array} dtypes3 - list of supported data types for the output
@param {Options} [options] - options
@param {boolean} [options.enums=false] - boolean flag indicating whether to return signatures as a list of enumeration constants
@throws {TypeError} must provide recognized data types
@returns {Array} strided array containing binary interface signatures

@example
var dtypes = [
    'float64',
    'float32',
    'int32',
    'uint8'
];

var sigs = signatures( dtypes, dtypes, dtypes );
// returns [ 'float32', 'float32', 'float32', ... ]