function labSend(X, labTarget, varargin)
%LABSEND Send gpuArray data to another lab
%       labSend(data,destination)
%       labSend(data,destination,tag)
%   
%       This function returns after the corresponding labReceive operation 
%       completes.
%   
%       For non-sparse data, labSend uses fast peer-to-peer GPU communication, 
%       including NVLink, if available.
%   
%   See also LABBARRIER, LABBROADCAST, LABRECEIVE, LABSENDRECEIVE, LABINDEX, NUMLABS


%   Copyright 2020 The MathWorks, Inc.

% We use the deferred communicaton object (GpuCommsDeferred) to redirect the workflow to
% a GPU optimised version of labSend - labReceive.
% We send the GpuCommsDeferred object so that when we load the object we will be
% able to "overload" labReceive with our GPU-specific version.

    narginchk(2,3);
    % Check that labTarget is a scalar or a vector
    if ~isvector(labTarget)
        error(message('parallel:lang:mpi:SendFailed'));
    end
    % GPU optimised version is available for non-sparse gpuArrays and only
    % when both the sender and the receiver have a suitable GPU.
    optimisedSend = isgpuarray(X) && existsOnGPU(X) && ~issparse(X);
    useFallback = true(size(labTarget));
    if optimisedSend
        deferredObj = parallel.internal.gpumpi.GpuCommsDeferred(X, varargin{:});
        for i = 1:length(labTarget)
            parallel.internal.mpi.labSsend(deferredObj, labTarget(i), varargin{:});
            useFallback(i) = parallel.internal.gpumpi.gpuLabSend(X, labTarget(i), varargin{:});
        end
    end
    useFallbackLabTargets = labTarget(useFallback);
    if ~isempty(useFallbackLabTargets)
        % GPU optimised version not available so fall back to the general
        % communication.
        builtin('labSend', X, useFallbackLabTargets, varargin{:});
    end
end
