309 lines
8.6 KiB
JavaScript
309 lines
8.6 KiB
JavaScript
|
/**
|
||
|
* @license Apache-2.0
|
||
|
*
|
||
|
* Copyright (c) 2018 The Stdlib Authors.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
// MODULES //
|
||
|
|
||
|
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
|
||
|
var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive;
|
||
|
var isCollection = require( '@stdlib/assert/is-collection' );
|
||
|
var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' );
|
||
|
var isArrayBuffer = require( '@stdlib/assert/is-arraybuffer' );
|
||
|
var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
|
||
|
var setReadOnlyAccessor = require( '@stdlib/utils/define-nonenumerable-read-only-accessor' );
|
||
|
var ctors = require( './../../typed-ctors' );
|
||
|
var copy = require( '@stdlib/utils/copy' );
|
||
|
var ArrayBuffer = require( './../../buffer' );
|
||
|
var ceil = require( '@stdlib/math/base/special/ceil' );
|
||
|
var floor = require( '@stdlib/math/base/special/floor' );
|
||
|
var ceil2 = require( '@stdlib/math/base/special/ceil2' );
|
||
|
var log2 = require( '@stdlib/math/base/special/log2' );
|
||
|
var min = require( '@stdlib/math/base/special/min' );
|
||
|
var defaults = require( './defaults.json' );
|
||
|
var validate = require( './validate.js' );
|
||
|
var createPool = require( './pool.js' );
|
||
|
var BYTES_PER_ELEMENT = require( './bytes_per_element.json' );
|
||
|
|
||
|
|
||
|
// MAIN //
|
||
|
|
||
|
/**
|
||
|
* Creates a typed array pool.
|
||
|
*
|
||
|
* @param {Options} [options] - pool options
|
||
|
* @param {NonNegativeInteger} [options.highWaterMark] - maximum total memory which can be allocated
|
||
|
* @throws {TypeError} options argument must be an object
|
||
|
* @throws {TypeError} must provide valid options
|
||
|
* @returns {Function} allocator
|
||
|
*
|
||
|
* @example
|
||
|
* var typedarraypool = factory();
|
||
|
*
|
||
|
* // Allocate an array of doubles:
|
||
|
* var arr = typedarraypool( 5, 'float64' );
|
||
|
* // returns <Float64Array>[ 0.0, 0.0, 0.0, 0.0, 0.0 ]
|
||
|
*
|
||
|
* arr[ 0 ] = 3.14;
|
||
|
* arr[ 1 ] = 3.14;
|
||
|
*
|
||
|
* // ...
|
||
|
*
|
||
|
* // Free the allocated memory to be used in a future allocation:
|
||
|
* typedarraypool.free( arr );
|
||
|
*/
|
||
|
function factory( options ) {
|
||
|
var nbytes;
|
||
|
var pool;
|
||
|
var opts;
|
||
|
var err;
|
||
|
|
||
|
opts = copy( defaults );
|
||
|
if ( arguments.length ) {
|
||
|
err = validate( opts, options );
|
||
|
if ( err ) {
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
pool = createPool( ceil( log2( opts.highWaterMark ) ) );
|
||
|
nbytes = 0;
|
||
|
|
||
|
setReadOnly( malloc, 'malloc', malloc ); // circular reference
|
||
|
setReadOnly( malloc, 'calloc', calloc );
|
||
|
setReadOnly( malloc, 'free', free );
|
||
|
setReadOnly( malloc, 'clear', clear );
|
||
|
setReadOnly( malloc, 'highWaterMark', opts.highWaterMark );
|
||
|
setReadOnlyAccessor( malloc, 'nbytes', getBytes );
|
||
|
|
||
|
return malloc;
|
||
|
|
||
|
/**
|
||
|
* Returns the number of allocated bytes.
|
||
|
*
|
||
|
* @private
|
||
|
* @returns {NonNegativeInteger} number of allocated bytes
|
||
|
*/
|
||
|
function getBytes() {
|
||
|
return nbytes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array buffer.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {NonNegativeInteger} n - number of bytes
|
||
|
* @returns {(ArrayBuffer|null)} array buffer or null
|
||
|
*/
|
||
|
function arraybuffer( n ) {
|
||
|
var buf;
|
||
|
var i;
|
||
|
|
||
|
// Convert the number of bytes to an index in our pool table:
|
||
|
i = log2( n );
|
||
|
|
||
|
// If we already have an available array buffer, use it...
|
||
|
if ( i < pool.length && pool[ i ].length ) {
|
||
|
return pool[ i ].pop();
|
||
|
}
|
||
|
// Before allocating a new array buffer, ensure that we have not exceeded the maximum number of bytes we are allowed to allocate...
|
||
|
if ( nbytes+n > opts.highWaterMark ) {
|
||
|
return null;
|
||
|
}
|
||
|
buf = new ArrayBuffer( n );
|
||
|
|
||
|
// Update the running counter of allocated bytes:
|
||
|
nbytes += n;
|
||
|
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a typed array.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {Function} ctor - typed array constructor
|
||
|
* @param {NonNegativeInteger} len - view length
|
||
|
* @param {string} dtype - data type
|
||
|
* @returns {(TypedArray|null)} typed array or null
|
||
|
*/
|
||
|
function typedarray( ctor, len, dtype ) {
|
||
|
var buf;
|
||
|
if ( len === 0 ) {
|
||
|
return new ctor( 0 );
|
||
|
}
|
||
|
buf = arraybuffer( ceil2( len )*BYTES_PER_ELEMENT[ dtype ] );
|
||
|
if ( buf === null ) {
|
||
|
return buf;
|
||
|
}
|
||
|
return new ctor( buf, 0, len );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an uninitialized typed array.
|
||
|
*
|
||
|
* ## Notes
|
||
|
*
|
||
|
* - Memory is **not** initialized.
|
||
|
* - Memory is lazily allocated.
|
||
|
* - If the function returns `null`, the function was unable to allocate a new typed array from the typed array pool (most likely due to insufficient memory).
|
||
|
*
|
||
|
* @private
|
||
|
* @param {(NonNegativeInteger|Collection)} [arg] - an array length or an array-like object
|
||
|
* @param {string} [dtype="float64"] - data type
|
||
|
* @throws {TypeError} must provide a valid array length or an array-like object
|
||
|
* @throws {TypeError} must provide a recognized data type
|
||
|
* @returns {(TypedArray|null)} typed array or null
|
||
|
*/
|
||
|
function malloc() {
|
||
|
var nargs;
|
||
|
var dtype;
|
||
|
var ctor;
|
||
|
var len;
|
||
|
var arr;
|
||
|
var out;
|
||
|
var i;
|
||
|
|
||
|
nargs = arguments.length;
|
||
|
if ( nargs && isString( arguments[ nargs-1 ] ) ) {
|
||
|
nargs -= 1;
|
||
|
dtype = arguments[ nargs ];
|
||
|
} else {
|
||
|
dtype = 'float64';
|
||
|
}
|
||
|
ctor = ctors( dtype );
|
||
|
if ( ctor === null ) {
|
||
|
throw new TypeError( 'invalid argument. Must provide a recognized data type. Value: `'+dtype+'`.' );
|
||
|
}
|
||
|
if ( nargs <= 0 ) {
|
||
|
return new ctor( 0 );
|
||
|
}
|
||
|
// Check if provided a typed array length...
|
||
|
if ( isNonNegativeInteger( arguments[ 0 ] ) ) {
|
||
|
return typedarray( ctor, arguments[ 0 ], dtype );
|
||
|
}
|
||
|
// Check if provided an array-like object containing data elements...
|
||
|
if ( isCollection( arguments[ 0 ] ) ) {
|
||
|
arr = arguments[ 0 ];
|
||
|
len = arr.length;
|
||
|
out = typedarray( ctor, len, dtype );
|
||
|
if ( out === null ) {
|
||
|
return out;
|
||
|
}
|
||
|
for ( i = 0; i < len; i++ ) {
|
||
|
out[ i ] = arr[ i ];
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
throw new TypeError( 'invalid argument. First argument must be either an array length or an array-like object. Value: `'+arguments[ 0 ]+'`.' );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a zero-initialized typed array.
|
||
|
*
|
||
|
* ## Notes
|
||
|
*
|
||
|
* - If the function returns `null`, the function was unable to allocate a new typed array from the typed array pool (most likely due to insufficient memory).
|
||
|
*
|
||
|
* @private
|
||
|
* @param {NonNegativeInteger} [len=0] - array length
|
||
|
* @param {string} [dtype="float64"] - data type
|
||
|
* @throws {TypeError} must provide a valid array length
|
||
|
* @throws {TypeError} must provide a recognized data type
|
||
|
* @returns {(TypedArray|null)} typed array or null
|
||
|
*/
|
||
|
function calloc() {
|
||
|
var nargs;
|
||
|
var out;
|
||
|
var i;
|
||
|
|
||
|
nargs = arguments.length;
|
||
|
if ( nargs === 0 ) {
|
||
|
out = malloc();
|
||
|
} else if ( nargs === 1 ) {
|
||
|
out = malloc( arguments[ 0 ] );
|
||
|
} else {
|
||
|
out = malloc( arguments[ 0 ], arguments[ 1 ] );
|
||
|
}
|
||
|
if ( out !== null ) {
|
||
|
// Initialize the memory...
|
||
|
for ( i = 0; i < out.length; i++ ) {
|
||
|
out[ i ] = 0.0;
|
||
|
}
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Frees a typed array or typed array buffer.
|
||
|
*
|
||
|
* ## Notes
|
||
|
*
|
||
|
* - Implicitly, we support providing non-internally allocated arrays and array buffer (e.g., "freeing" a typed array allocated in userland); however, the freed array buffer is likely to have excess capacity when compared to other members in its pool.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {(TypedArray|ArrayBuffer)} buf - typed array or array buffer to free
|
||
|
* @throws {TypeError} must provide a typed array or typed array buffer
|
||
|
* @returns {boolean} boolean indicating whether the typed array or array buffer was successfully freed
|
||
|
*/
|
||
|
function free( buf ) {
|
||
|
var n;
|
||
|
var p;
|
||
|
var i;
|
||
|
if ( isTypedArrayLike( buf ) && buf.buffer ) {
|
||
|
buf = buf.buffer;
|
||
|
} else if ( !isArrayBuffer( buf ) ) {
|
||
|
throw new TypeError( 'invalid argument. Must provide a typed array or typed array buffer. Value: `'+buf+'`.' );
|
||
|
}
|
||
|
if ( buf.byteLength > 0 ) {
|
||
|
n = floor( log2( buf.byteLength ) );
|
||
|
|
||
|
// Prohibit "freeing" array buffers which would potentially allow users to circumvent high water mark limits:
|
||
|
n = min( pool.length-1, n );
|
||
|
|
||
|
// Ensure that we do not attempt to free the same buffer more than once...
|
||
|
p = pool[ n ];
|
||
|
for ( i = 0; i < p.length; i++ ) {
|
||
|
if ( p[ i ] === buf ) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
// Add the buffer to our pool of free buffers:
|
||
|
p.push( buf );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears the typed array pool allowing garbage collection of previously allocated (and currently free) array buffers.
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
function clear() {
|
||
|
var i;
|
||
|
for ( i = 0; i < pool.length; i++ ) {
|
||
|
pool[ i ].length = 0;
|
||
|
}
|
||
|
nbytes = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// EXPORTS //
|
||
|
|
||
|
module.exports = factory;
|