|  | 
|  | 1 | +module BinaryOps | 
|  | 2 | +import ..GrB | 
|  | 3 | +import ..GrB: | 
|  | 4 | +    Itypes, Ftypes, Ztypes, FZtypes, Rtypes, | 
|  | 5 | +    Ntypes, Ttypes, suffix, load_global, | 
|  | 6 | +    set!, GB_promote, | 
|  | 7 | +    OperatorCompiler, LibGraphBLAS, | 
|  | 8 | +    firsti0, firsti, firstj0, firstj, secondi0, secondi, secondj0,  | 
|  | 9 | +    secondj, ispair, second, rminus, ∨, ∧, lxor, xnor, bxnor,  | 
|  | 10 | +    bget, bset, bclr, GxB_fprint, inputisany, gxbprint, domains, | 
|  | 11 | +    BinaryOp | 
|  | 12 | + | 
|  | 13 | +import SparseBase: storedeltype | 
|  | 14 | + | 
|  | 15 | +domains(::BinaryOp{F, F2, X, Y, Z}) where {F, F2, X, Y, Z} = (X, Y) => Z | 
|  | 16 | + | 
|  | 17 | +""" | 
|  | 18 | +    cbinary(f::Function, ::Type{X}, ::Type{Y}, ::Type{Z}) -> Cvoid | 
|  | 19 | +
 | 
|  | 20 | +Create a C function pointer for a binary function `f` with signature  | 
|  | 21 | +    `f(z::Ptr{Z}, x::Ptr{X}, y::Ptr{Y})::Nothing`. | 
|  | 22 | +
 | 
|  | 23 | +This is used to supply a function pointer to a `GrB_BinaryOp` from Julia function `f`.  | 
|  | 24 | +On certain platforms closure functions are not supported (e.g. Aarch64),  | 
|  | 25 | +this function will throw an `ArgumentError` in those cases. | 
|  | 26 | +""" | 
|  | 27 | +@generated function cbinary(f::F, ::Type{X}, ::Type{Y}, ::Type{Z}) where {F, X, Y, Z} | 
|  | 28 | +    if Base.issingletontype(F) | 
|  | 29 | +        :(@cfunction($(F.instance), Cvoid, (Ptr{Z}, Ptr{X}, Ptr{Y}))) | 
|  | 30 | +    else | 
|  | 31 | +        throw("Unsupported function $f.") | 
|  | 32 | +    end | 
|  | 33 | +end | 
|  | 34 | + | 
|  | 35 | +function Base.unsafe_convert(::Type{LibGraphBLAS.GrB_BinaryOp}, op::BinaryOp{F, F2, X, Y, Z}) where {F, F2, X, Y, Z} | 
|  | 36 | +    if !op.loaded | 
|  | 37 | +        if op.builtin | 
|  | 38 | +            op.p = load_global(op.typestr, LibGraphBLAS.GrB_BinaryOp) | 
|  | 39 | +        else | 
|  | 40 | +            opref = Ref{LibGraphBLAS.GrB_BinaryOp}() | 
|  | 41 | +            z, x, y = GrB.Type(Z), GrB.Type(X), GrB.Type(Y) | 
|  | 42 | +            info = LibGraphBLAS.GxB_BinaryOp_new( | 
|  | 43 | +                opref,  | 
|  | 44 | +                cbinary(op.c_fn, X, Y, Z),  | 
|  | 45 | +                z, x, y,  | 
|  | 46 | +                op.maycompile ? op.typestr : C_NULL,  | 
|  | 47 | +                op.maycompile ? "GB_ISOBJ $(op.bitcodepath)" : C_NULL | 
|  | 48 | +            ) | 
|  | 49 | +            if info != LibGraphBLAS.GrB_SUCCESS | 
|  | 50 | +                GrB.@uninitializedobject info z x y | 
|  | 51 | +                GrB.@fallbackerror info | 
|  | 52 | +            end | 
|  | 53 | +            op.p = opref[] | 
|  | 54 | +            op.compileset = op.maycompile | 
|  | 55 | +        end | 
|  | 56 | +        op.loaded = true | 
|  | 57 | +        !op.builtin && set!(op, :name, string(op.fn)) | 
|  | 58 | +    end | 
|  | 59 | +    if !op.loaded | 
|  | 60 | +        error("This operator $(op.fn) could not be loaded, and is invalid.") | 
|  | 61 | +    else | 
|  | 62 | +        if op.maycompile && !op.compileset | 
|  | 63 | +            set!(op, :jit_cname, op.typestr) | 
|  | 64 | +            set!(op, :jit_cdef, "GB_ISOBJ $(op.bitcodepath)") | 
|  | 65 | +            op.compileset = true | 
|  | 66 | +        end | 
|  | 67 | +        return op.p | 
|  | 68 | +    end | 
|  | 69 | +end | 
|  | 70 | +function GrB.nothrow_wait!(op::BinaryOp, mode)  | 
|  | 71 | +    return LibGraphBLAS.GrB_BinaryOp_wait(op, mode) | 
|  | 72 | +end | 
|  | 73 | +function GrB.wait!(op::BinaryOp, mode)  | 
|  | 74 | +    info = GrB.nothrow_wait!(op, mode) | 
|  | 75 | +    if info != LibGraphBLAS.GrB_SUCCESS | 
|  | 76 | +        # Technically pending OOB can throw here, but I don't see how on a BinaryOp. | 
|  | 77 | +        GrB.@invalidvalue info mode | 
|  | 78 | +        GrB.@uninitializedobject info op | 
|  | 79 | +        GrB.@fallbackerror info | 
|  | 80 | +    end | 
|  | 81 | +end | 
|  | 82 | + | 
|  | 83 | +const BINARYOPS = Dict() | 
|  | 84 | +const COMPILEDBINARYOPS = Dict() | 
|  | 85 | +const BUILTINBINARYOPS = IdDict{<:Any, <:Any}() # (f) -> (name, inputtype(s)) | 
|  | 86 | + | 
|  | 87 | +function linker(job, compiled) | 
|  | 88 | +    irpath, name = OperatorCompiler.writeir(compiled) | 
|  | 89 | +    (; fn, c_fn, intypes) = job.config.params | 
|  | 90 | +    return BinaryOp{ | 
|  | 91 | +        typeof(fn),  | 
|  | 92 | +        storedeltype(intypes[2]),  | 
|  | 93 | +        storedeltype(intypes[3]),  | 
|  | 94 | +        storedeltype(intypes[1]) | 
|  | 95 | +    }( | 
|  | 96 | +        false, false, name, fn, c_fn; | 
|  | 97 | +        maycompile = true, irpath | 
|  | 98 | +    ) | 
|  | 99 | +end | 
|  | 100 | + | 
|  | 101 | +function binarycachecompile( | 
|  | 102 | +    f, ptrfunction, ::Type{X}, ::Type{Y}, ::Type{Z};  | 
|  | 103 | +    maycompile = true | 
|  | 104 | +) where {X, Y, Z} | 
|  | 105 | +    job = OperatorCompiler.operatorjob(f, ptrfunction, (Ptr{Z}, Ptr{X}, Ptr{Y})) | 
|  | 106 | +    op = OperatorCompiler.cached_compile(COMPILEDBINARYOPS, job, linker) | 
|  | 107 | +    op.maycompile = op.maycompile || maycompile | 
|  | 108 | +    return op | 
|  | 109 | +end | 
|  | 110 | + | 
|  | 111 | +function binarybuiltin(f, X, Y, Z, builtin_name) | 
|  | 112 | +    namestr = string(builtin_name[1]) *  | 
|  | 113 | +        (Any ∈ builtin_name[2] ? "_$(suffix(Z))" : "_$(suffix(GB_promote(X, Y)))") | 
|  | 114 | +    namestr = (X <: Complex || Y <: Complex) ? "GxB" * namestr[4:end] : namestr | 
|  | 115 | +    return BinaryOp{typeof(f), X, Y, Z}( | 
|  | 116 | +        true, false, namestr, f | 
|  | 117 | +    ) | 
|  | 118 | +end | 
|  | 119 | + | 
|  | 120 | +""" | 
|  | 121 | +    BinaryOp(f::Function, ::Type{X}, ::Type{Y}, ::Type{Z}) -> UnaryOp{F, F2, X, Y, Z} | 
|  | 122 | +
 | 
|  | 123 | +Create a `GrB_UnaryOp` from a Julia function `f` with signature `f(x::X, y::Y) -> z::Z`. | 
|  | 124 | +
 | 
|  | 125 | +Most users should not call this function directly,  | 
|  | 126 | +    instead pass a function directly to a GraphBLAS operation. | 
|  | 127 | +
 | 
|  | 128 | +# Arguments | 
|  | 129 | +- `f`: Julia function to wrap. | 
|  | 130 | +- `X::DataType`: Type of the first operand. | 
|  | 131 | +- `Y::DataType`: Type of the second operand. | 
|  | 132 | +- `Z::DataType`: Output type. | 
|  | 133 | +- `maycompile::Bool`: If `maycompile` is `true`, JIT compilation may be performed.  | 
|  | 134 | +If false, function pointers will be used. Note: this is currently a one way switch,  | 
|  | 135 | +it may be turned from `false` to `true`, but not back to `false`. | 
|  | 136 | +
 | 
|  | 137 | +Function `f` must not allocate, yield or throw. | 
|  | 138 | +""" | 
|  | 139 | +function BinaryOp( | 
|  | 140 | +    f, ::Type{X}, ::Type{Y}, ::Type{Z}; maycompile = true | 
|  | 141 | +) where {X, Y, Z} | 
|  | 142 | +    # some binaryops take any types, including UDTs | 
|  | 143 | +    x, y, z = inputisany(f) ? (Any, Any, Int64) : (X, Y, Z) | 
|  | 144 | + | 
|  | 145 | +    maybeop = Base.get!(BINARYOPS, (f, x, y, z)) do | 
|  | 146 | +        builtin_name = Base.get(BUILTINBINARYOPS, f, nothing) | 
|  | 147 | +        if builtin_name !== nothing && ( | 
|  | 148 | +            (x ∈ builtin_name[2] && y ∈ builtin_name[2]) | 
|  | 149 | +        ) | 
|  | 150 | +            return binarybuiltin(f, x, y, z, builtin_name) | 
|  | 151 | +        else | 
|  | 152 | +            return nothing | 
|  | 153 | +        end | 
|  | 154 | +    end | 
|  | 155 | +    if maybeop isa BinaryOp | 
|  | 156 | +        return maybeop | 
|  | 157 | +    elseif maybeop === nothing | 
|  | 158 | +        function binaryopfn(z, x, y) | 
|  | 159 | +            Base.unsafe_store!(z, f(Base.unsafe_load(x), Base.unsafe_load(y))) | 
|  | 160 | +            return nothing | 
|  | 161 | +        end | 
|  | 162 | +        BINARYOPS[(f, x, y, z)] = binaryopfn | 
|  | 163 | +        return binarycachecompile(f, binaryopfn, X, Y, Z; maycompile) | 
|  | 164 | +    else | 
|  | 165 | +        return binarycachecompile(f, maybeop, X, Y, Z; maycompile) | 
|  | 166 | +    end | 
|  | 167 | +end | 
|  | 168 | + | 
|  | 169 | +BinaryOp(f, X, Y, Z; maycompile = true) =  | 
|  | 170 | +    BinaryOp(f, storedeltype(X), storedeltype(Y), storedeltype(Z); maycompile) | 
|  | 171 | +BinaryOp( | 
|  | 172 | +    op::BinaryOp, ::Type{X}, ::Type{Y}, ::Type{Z}; kwargs... | 
|  | 173 | +) where {X, Y, Z} = op | 
|  | 174 | +BinaryOp( | 
|  | 175 | +    f, T; maycompile = true | 
|  | 176 | +) = BinaryOp(f, T, T, T; maycompile) | 
|  | 177 | + | 
|  | 178 | +# TODO: this can and will throw INVALID_OBJECT instead of uninitializedobject | 
|  | 179 | +# We should then be able to capture it better? Still could be another pending object. | 
|  | 180 | +function GrB.GxB_fprint(x::BinaryOp, name, level, file) | 
|  | 181 | +    info = LibGraphBLAS.GxB_BinaryOp_fprint(x, name, level, file) | 
|  | 182 | +    if info != LibGraphBLAS.GrB_SUCCESS | 
|  | 183 | +        GrB.@uninitializedobject info x | 
|  | 184 | +        GrB.@fallbackerror info | 
|  | 185 | +    end | 
|  | 186 | +end | 
|  | 187 | +function Base.show(io::IO, ::MIME"text/plain", t::BinaryOp{F, F2, X, Y, Z}) where {F, F2, X, Y, Z} | 
|  | 188 | +    print(io, "GrB_BinaryOp{$(string(F))($X, $Y) -> $Z}: ") | 
|  | 189 | +    gxbprint(io, t) | 
|  | 190 | +end | 
|  | 191 | + | 
|  | 192 | +# All types | 
|  | 193 | +BUILTINBINARYOPS[first] = :GrB_FIRST, Ttypes | 
|  | 194 | +BUILTINBINARYOPS[second] = :GrB_SECOND, Ttypes | 
|  | 195 | +BUILTINBINARYOPS[any] = :GxB_ANY, Ttypes # this doesn't match the semantics of Julia's any, but that may be  | 
|  | 196 | + | 
|  | 197 | +BUILTINBINARYOPS[ispair] = :GrB_ONEB, Ttypes | 
|  | 198 | +BUILTINBINARYOPS[+] = :GrB_PLUS, Ttypes | 
|  | 199 | +BUILTINBINARYOPS[-] = :GrB_MINUS, Ttypes | 
|  | 200 | + | 
|  | 201 | +BUILTINBINARYOPS[rminus] = :GxB_RMINUS, Ttypes | 
|  | 202 | + | 
|  | 203 | +BUILTINBINARYOPS[*] = :GrB_TIMES, Ttypes | 
|  | 204 | +BUILTINBINARYOPS[/] = :GrB_DIV, Ttypes | 
|  | 205 | +BUILTINBINARYOPS[\] = :GxB_RDIV, Ttypes | 
|  | 206 | +BUILTINBINARYOPS[^] = :GxB_POW, Ttypes | 
|  | 207 | + | 
|  | 208 | + | 
|  | 209 | + | 
|  | 210 | +# Real types | 
|  | 211 | +BUILTINBINARYOPS[min] = :GrB_MIN, Rtypes | 
|  | 212 | +BUILTINBINARYOPS[max] = :GrB_MAX, Rtypes | 
|  | 213 | + | 
|  | 214 | +# These are not in top level namespace because they should | 
|  | 215 | +# only be accessible via GrB_BinaryOp construction of | 
|  | 216 | +# the operators. | 
|  | 217 | +iseq(x::T, y::U) where {T, U} = promote_type(T, U)(x == y) | 
|  | 218 | +isne(x::T, y::U) where {T, U} = promote_type(T, U)(x != y) | 
|  | 219 | +isgt(x::T, y::U) where {T, U} = promote_type(T, U)(x > y) | 
|  | 220 | +islt(x::T, y::U) where {T, U} = promote_type(T, U)(x < y) | 
|  | 221 | +isge(x::T, y::U) where {T, U} = promote_type(T, U)(x >= y) | 
|  | 222 | +isle(x::T, y::U) where {T, U} = promote_type(T, U)(x <= y) | 
|  | 223 | +BUILTINBINARYOPS[iseq] = :GxB_ISEQ, Ttypes | 
|  | 224 | +BUILTINBINARYOPS[isne] = :GxB_ISNE, Ttypes | 
|  | 225 | +BUILTINBINARYOPS[isgt] = :GxB_ISGT, Rtypes | 
|  | 226 | +BUILTINBINARYOPS[islt] = :GxB_ISLT, Rtypes | 
|  | 227 | +BUILTINBINARYOPS[isge] = :GxB_ISGE, Rtypes | 
|  | 228 | +BUILTINBINARYOPS[isle] = :GxB_ISLE, Rtypes | 
|  | 229 | + | 
|  | 230 | +BUILTINBINARYOPS[(∨)] = :GxB_LOR, Rtypes | 
|  | 231 | + | 
|  | 232 | +BUILTINBINARYOPS[(∧)] = :GxB_LAND, Rtypes | 
|  | 233 | + | 
|  | 234 | +const LOR_BOOL = binarybuiltin(|, Bool, Bool, Bool, "GxB_LOR_BOOL") | 
|  | 235 | +const LAND_BOOL = binarybuiltin(&, Bool, Bool, Bool, "GxB_LAND_BOOL") | 
|  | 236 | + | 
|  | 237 | +BinaryOp(::typeof(|), ::Type{Bool}, ::Type{Bool}, ::Type{Bool}) = LOR_BOOL | 
|  | 238 | +BinaryOp(::typeof(&), ::Type{Bool}, ::Type{Bool}, ::Type{Bool}) = LAND_BOOL | 
|  | 239 | + | 
|  | 240 | +BUILTINBINARYOPS[lxor] = :GxB_LXOR, Rtypes | 
|  | 241 | + | 
|  | 242 | +# T/R => Bool | 
|  | 243 | +BUILTINBINARYOPS[(==)] = :GrB_EQ, Ttypes | 
|  | 244 | +BUILTINBINARYOPS[(!=)] = :GrB_NE, Ttypes | 
|  | 245 | +BUILTINBINARYOPS[(>)] = :GrB_GT, Rtypes | 
|  | 246 | +BUILTINBINARYOPS[(<)] = :GrB_LT, Rtypes | 
|  | 247 | +BUILTINBINARYOPS[(>=)] = :GrB_GE, Rtypes | 
|  | 248 | +BUILTINBINARYOPS[(<=)] = :GrB_LE, Rtypes | 
|  | 249 | + | 
|  | 250 | +# Bool=>Bool, most of which are covered above. | 
|  | 251 | +BUILTINBINARYOPS[xnor] = :GrB_LXNOR, (Bool,) | 
|  | 252 | + | 
|  | 253 | + | 
|  | 254 | +BUILTINBINARYOPS[atan] = :GxB_ATAN2, Ftypes | 
|  | 255 | +BUILTINBINARYOPS[hypot] = :GxB_HYPOT, Ftypes | 
|  | 256 | +BUILTINBINARYOPS[mod] = :GxB_FMOD, Ftypes | 
|  | 257 | +BUILTINBINARYOPS[rem] = :GxB_REMAINDER, Ftypes | 
|  | 258 | +BUILTINBINARYOPS[ldexp] = :GxB_LDEXP, Ftypes | 
|  | 259 | +BUILTINBINARYOPS[copysign] = :GxB_COPYSIGN, Ftypes | 
|  | 260 | +BUILTINBINARYOPS[complex] = :GxB_CMPLX, Ftypes | 
|  | 261 | + | 
|  | 262 | +# bitwise | 
|  | 263 | +BUILTINBINARYOPS[(|)] = :GrB_BOR, Itypes | 
|  | 264 | +BUILTINBINARYOPS[(&)] = :GrB_BAND, Itypes | 
|  | 265 | +BUILTINBINARYOPS[⊻] = :GrB_BXOR, Itypes | 
|  | 266 | +BUILTINBINARYOPS[bxnor] = :GrB_BXNOR, Itypes | 
|  | 267 | +const LXOR_BOOL = binarybuiltin(⊻, Bool, Bool, Bool, "GxB_LXOR_BOOL") | 
|  | 268 | +BinaryOp(::typeof(⊻), ::Type{Bool}, ::Type{Bool}, ::Type{Bool}) = LXOR_BOOL | 
|  | 269 | + | 
|  | 270 | +# leaving these without any equivalent Julia functions | 
|  | 271 | +# probably should only operate on Ints anyway. | 
|  | 272 | + | 
|  | 273 | +BUILTINBINARYOPS[bget] = :GxB_BGET, Itypes | 
|  | 274 | +BUILTINBINARYOPS[bset] = :GxB_BSET, Itypes | 
|  | 275 | +BUILTINBINARYOPS[bclr] = :GxB_BCLR, Itypes | 
|  | 276 | + | 
|  | 277 | +BUILTINBINARYOPS[(>>)] = :GxB_BSHIFT, (Itypes..., Int8) | 
|  | 278 | + | 
|  | 279 | + | 
|  | 280 | +# Positionals with dummy functions for output type inference purposes | 
|  | 281 | +BUILTINBINARYOPS[firsti0] = :GxB_FIRSTI, (Any,) | 
|  | 282 | +BUILTINBINARYOPS[firsti] =  :GxB_FIRSTI1, (Any,) | 
|  | 283 | + | 
|  | 284 | +BUILTINBINARYOPS[firstj0] = :GxB_FIRSTJ, (Any,) | 
|  | 285 | +BUILTINBINARYOPS[firstj] = :GxB_FIRSTJ1, (Any,) | 
|  | 286 | + | 
|  | 287 | +BUILTINBINARYOPS[secondi0] = :GxB_SECONDI, (Any,) | 
|  | 288 | +BUILTINBINARYOPS[secondi] = :GxB_SECONDI1, (Any,) | 
|  | 289 | + | 
|  | 290 | +BUILTINBINARYOPS[secondj0] = :GxB_SECONDJ, (Any,) | 
|  | 291 | +BUILTINBINARYOPS[secondj] = :GxB_SECONDJ1, (Any,) | 
|  | 292 | + | 
|  | 293 | +GrB.inputisany(::typeof(firsti0)) = true | 
|  | 294 | +GrB.inputisany(::typeof(firsti)) = true | 
|  | 295 | +GrB.inputisany(::typeof(firstj0)) = true | 
|  | 296 | +GrB.inputisany(::typeof(firstj)) = true | 
|  | 297 | +GrB.inputisany(::typeof(secondi0)) = true | 
|  | 298 | +GrB.inputisany(::typeof(secondi)) = true | 
|  | 299 | +GrB.inputisany(::typeof(secondj0)) = true | 
|  | 300 | +GrB.inputisany(::typeof(secondj)) = true | 
|  | 301 | + | 
|  | 302 | +end | 
0 commit comments