├── .gitignore ├── Changes ├── MANIFEST.SKIP ├── Makefile.PL ├── TensorFlow.xs ├── fix_inline.h ├── lib └── ML │ ├── TensorFlow.pm │ └── TensorFlow │ └── CAPI.pm ├── ppport.h ├── t ├── 00_load.t ├── 01_status.t ├── 02_misc_basic_constructors.t ├── 03_buffer.t └── 05_tensor.t └── typemap /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | *.bak 5 | /Makefile 6 | Makefile.old 7 | blib/ 8 | pm_to_blib 9 | MYMETA.* 10 | TensorFlow.bs 11 | TensorFlow.so 12 | TensorFlow.c 13 | libtensorflow.so 14 | *.o 15 | nohup.out 16 | t.pl 17 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension ML::TensorFlow. 2 | 3 | 0.01 Wed Nov 30 20:00:00 2016 4 | - original version 5 | 6 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | # Avoid archives of this distribution 2 | \bML-TensorFlow-[\d\.\_]+ 3 | 4 | # Avoid version control files. 5 | \bRCS\b 6 | \bCVS\b 7 | ,v$ 8 | \B\.svn\b 9 | \B\.cvsignore$ 10 | \B\.gitignore$ 11 | \B\.git 12 | 13 | # Avoid Makemaker generated and utility files. 14 | \bMakefile$ 15 | \bblib 16 | \bMakeMaker-\d 17 | \bpm_to_blib$ 18 | \bblibdirs$ 19 | ^MANIFEST\.SKIP$ 20 | MYMETA\..*$ 21 | 22 | # Avoid Module::Build generated and utility files. 23 | \bBuild$ 24 | \bBuild.bat$ 25 | \b_build 26 | 27 | # Avoid Devel::Cover generated files 28 | \bcover_db 29 | 30 | # Avoid temp and backup files. 31 | ~$ 32 | \.tmp$ 33 | \.old$ 34 | \.bak$ 35 | \#$ 36 | \.# 37 | \.rej$ 38 | \.swp$ 39 | \.swo$ 40 | 41 | 42 | JumpHash\.(?:bs|c|o)$ 43 | \.o$ 44 | t\.pl$ 45 | 46 | # Avoid OS-specific files/dirs 47 | # Mac OSX metadata 48 | \B\.DS_Store 49 | # Mac OSX SMB mount metadata files 50 | \B\._ 51 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.014002; 2 | use ExtUtils::MakeMaker; 3 | # See lib/ExtUtils/MakeMaker.pm for details of how to influence 4 | # the contents of the Makefile that is written. 5 | WriteMakefile( 6 | NAME => 'ML::TensorFlow', 7 | VERSION_FROM => 'lib/ML/TensorFlow.pm', # finds $VERSION 8 | PREREQ_PM => { 9 | }, # e.g., Module::Name => 1.1 10 | ($] >= 5.005 ? ## Add these new keywords supported since 5.005 11 | (ABSTRACT_FROM => 'lib/ML/TensorFlow.pm', # retrieve abstract from module 12 | AUTHOR => 'Steffen Mueller ') : ()), 13 | LIBS => ['-L. -ltensorflow'], # e.g., '-lm' 14 | #DEFINE => '-O2', # e.g., '-DHAVE_SOMETHING' 15 | INC => '-I.', # e.g., '-I. -I/usr/include/other' 16 | OBJECT => '$(O_FILES)', # link all the C files too 17 | ); 18 | -------------------------------------------------------------------------------- /TensorFlow.xs: -------------------------------------------------------------------------------- 1 | #define PERL_NO_GET_CONTEXT /* we want efficiency */ 2 | #include "EXTERN.h" 3 | #include "perl.h" 4 | #include "XSUB.h" 5 | 6 | #include "ppport.h" 7 | #include "fix_inline.h" 8 | #include 9 | #include 10 | 11 | /* Copy/pasting part of the c_api.h API here is obviously dumb, 12 | * but given how tensorflow is built (Python module) and installed, 13 | * it seems like expecting to find c_api.h anywhere as part of an 14 | * install is... */ 15 | /* On second thought, reproducing the API using the FFI isn't any more 16 | * maintainable/more compatible unless one parses the header and 17 | * generates code from there. So meh. */ 18 | 19 | /* FIXME: Sync with c_api.h */ 20 | typedef struct { 21 | const void* data; 22 | size_t length; 23 | void (*data_deallocator)(void* data, size_t length); 24 | } TF_Buffer; 25 | 26 | extern TF_Buffer* TF_NewBufferFromString(const void* proto, size_t proto_len); 27 | 28 | /* Useful for passing *out* a protobuf. */ 29 | extern TF_Buffer* TF_NewBuffer(); 30 | 31 | extern void TF_DeleteBuffer(TF_Buffer*); 32 | 33 | /* END silly copy of partial c_api.h */ 34 | 35 | 36 | void safefree_deallocator(void *data, size_t length) 37 | { 38 | Safefree(data); 39 | } 40 | 41 | /* end c_api.h portion */ 42 | 43 | MODULE = ML::TensorFlow PACKAGE = ML::TensorFlow::CAPI 44 | PROTOTYPES: DISABLE 45 | 46 | SV * 47 | _make_perl_string_copy_from_opaque_string(SV *s_ptr, size_t len) 48 | CODE: 49 | RETVAL = newSVpvn((char *)SvIV(s_ptr), len); 50 | OUTPUT: RETVAL 51 | 52 | MODULE = ML::TensorFlow PACKAGE = ML::TensorFlow::Buffer 53 | PROTOTYPES: DISABLE 54 | 55 | UV 56 | _get_struct_size() 57 | CODE: 58 | RETVAL = (UV)sizeof(TF_Buffer); 59 | OUTPUT: RETVAL 60 | 61 | TF_Buffer* 62 | new(CLASS, ...) 63 | char *CLASS 64 | CODE: 65 | if (items == 1) { 66 | RETVAL = TF_NewBuffer(); 67 | } 68 | else if (items == 2) { 69 | STRLEN len; 70 | const char *str = SvPVbyte(ST(1), len); 71 | RETVAL = TF_NewBufferFromString((const void *)str, (size_t)len); 72 | } 73 | else { 74 | croak("Invalid number of arguments to buffer constructor"); 75 | } 76 | OUTPUT: RETVAL 77 | 78 | void 79 | DESTROY(self) 80 | TF_Buffer* self 81 | CODE: 82 | TF_DeleteBuffer(self); 83 | 84 | SV* 85 | get_data_copy(self) 86 | TF_Buffer* self 87 | PREINIT: 88 | CODE: 89 | RETVAL = newSVpvn((const char *)self->data, (STRLEN)self->length); 90 | OUTPUT: RETVAL 91 | 92 | SV* 93 | get_data_view(self) 94 | TF_Buffer* self 95 | PREINIT: 96 | CODE: 97 | RETVAL = newSV(0); 98 | /* read-only view - user beware of the memory management consequences */ 99 | SvUPGRADE(RETVAL, SVt_PV); 100 | SvPOK_on(RETVAL); 101 | SvPV_set(RETVAL, (char *)self->data); 102 | SvCUR_set(RETVAL, (STRLEN)self->length); 103 | SvLEN_set(RETVAL, 0); 104 | SvREADONLY_on(RETVAL); 105 | OUTPUT: RETVAL 106 | 107 | void 108 | set_data(self, data) 109 | TF_Buffer *self 110 | SV *data 111 | PREINIT: 112 | STRLEN len; 113 | const char *str; 114 | CODE: 115 | self->data_deallocator((void *)self->data, self->length); 116 | self->data_deallocator = &safefree_deallocator; 117 | 118 | str = SvPVbyte(data, len); 119 | self->length = (size_t)len; 120 | 121 | Newx(self->data, len, char); 122 | Copy(str, self->data, len, char); 123 | 124 | 125 | -------------------------------------------------------------------------------- /fix_inline.h: -------------------------------------------------------------------------------- 1 | #ifndef FIX_INLINE_H_ 2 | #define FIX_INLINE_H_ 3 | 4 | /* We do this because it seems that PERL_STATIC_INLINE isn't defined 5 | * or something like that. I haven't figured out why not. 6 | */ 7 | 8 | #ifndef PERL_STATIC_INLINE 9 | # ifdef NOINLINE 10 | # define PERL_STATIC_INLINE STATIC 11 | # elif defined(_MSC_VER) 12 | # define PERL_STATIC_INLINE STATIC __inline 13 | # else 14 | # define PERL_STATIC_INLINE STATIC inline 15 | # endif 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /lib/ML/TensorFlow.pm: -------------------------------------------------------------------------------- 1 | package ML::TensorFlow; 2 | use 5.14.2; 3 | use warnings; 4 | 5 | require Exporter; 6 | 7 | our $VERSION; 8 | BEGIN { $VERSION = '0.01'; } 9 | 10 | use XSLoader; 11 | BEGIN { XSLoader::load('ML::TensorFlow', $VERSION); } 12 | 13 | use Scalar::Util (); 14 | require bytes; 15 | 16 | use ML::TensorFlow::CAPI qw(:all); 17 | 18 | use Exporter 'import'; 19 | our @EXPORT_OK; 20 | BEGIN { 21 | push @EXPORT_OK, @ML::TensorFlow::CAPI::EXPORT_OK; 22 | } 23 | our %EXPORT_TAGS = (all => \@EXPORT_OK); 24 | 25 | use constant { 26 | Status => __PACKAGE__ . "::Status", 27 | Session => __PACKAGE__ . "::Session", 28 | SessionOptions => __PACKAGE__ . "::SessionOptions", 29 | Tensor => __PACKAGE__ . "::Tensor", 30 | Buffer => __PACKAGE__ . "::Buffer", 31 | Graph => __PACKAGE__ . "::Graph", 32 | }; 33 | push @EXPORT_OK, qw(Status SessionOptions Session Tensor Buffer Graph); 34 | 35 | package ML::TensorFlow::Status { 36 | sub new { 37 | my ($class) = @_; 38 | my $s = ML::TensorFlow::CAPI::TF_NewStatus(); 39 | my $self = bless(\$s => $class); 40 | # The following is not necessary since it's the default (but not documented as such?) 41 | #$self->set_status(ML::TensorFlow::CAPI::TF_Code_Enum()->{TF_OK}, ""); 42 | return $self; 43 | } 44 | 45 | sub DESTROY { 46 | my ($self) = @_; 47 | ML::TensorFlow::CAPI::TF_DeleteStatus($$self); 48 | } 49 | 50 | sub is_ok { 51 | my ($self) = @_; 52 | my $code = $self->get_code; 53 | return($code == ML::TensorFlow::CAPI::TF_Code_Enum()->{TF_OK}); 54 | } 55 | 56 | sub set_status { 57 | my ($self, $code, $message) = @_; 58 | ML::TensorFlow::CAPI::TF_SetStatus($$self, 0+$code, $message); 59 | } 60 | 61 | sub get_code { 62 | my ($self) = @_; 63 | return ML::TensorFlow::CAPI::TF_GetCode($$self); 64 | } 65 | 66 | sub get_message { 67 | my ($self) = @_; 68 | return ML::TensorFlow::CAPI::TF_Message($$self); 69 | } 70 | }; # end Status 71 | 72 | 73 | 74 | package ML::TensorFlow::SessionOptions { 75 | sub new { 76 | my ($class) = @_; 77 | my $s = ML::TensorFlow::CAPI::TF_NewSessionOptions(); 78 | my $self = bless(\$s => $class); 79 | 80 | $self->set_target(""); 81 | $self->set_config(""); 82 | return $self; 83 | } 84 | 85 | sub DESTROY { 86 | my ($self) = @_; 87 | ML::TensorFlow::CAPI::TF_DeleteSessionOptions($$self); 88 | } 89 | 90 | sub set_target { 91 | my ($self, $target) = @_; 92 | ML::TensorFlow::CAPI::TF_SetTarget($$self, $target); # FIXME don't know FFI::Platypus well enough, but normally in XS calls, there's a risk because Perl strings aren't guaranteed to be NUL terminated! 93 | } 94 | 95 | # TODO test 96 | sub set_config { 97 | my ($self, $protoblob) = @_; 98 | my $status = ML::TensorFlow::Status->new(); 99 | ML::TensorFlow::CAPI::TF_SetConfig($$self, $protoblob, bytes::length($protoblob), $$status); 100 | return $status; 101 | } 102 | 103 | }; # end SessionOptions 104 | 105 | 106 | # internal to ::Graph for now 107 | package ML::TensorFlow::_ImportGraphDefOptions { 108 | sub new { 109 | my ($class, $opt) = @_; 110 | $opt ||= {}; 111 | my $options = ML::TensorFlow::CAPI::TF_NewImportGraphDefOptions(); 112 | if (defined $opt->{prefix}) { 113 | ML::TensorFlow::CAPI::TF_ImportGraphDefOptionsSetPrefix($options, $opt->{prefix}); 114 | } 115 | return bless(\$options => $class); 116 | } 117 | 118 | sub DESTROY { 119 | my ($self) = @_; 120 | ML::TensorFlow::TF_DeleteImportGraphDefOptions($$self); 121 | } 122 | } # end ::_ImportGraphDefOptions 123 | 124 | package ML::TensorFlow::Graph { 125 | sub new { 126 | my ($class) = @_; 127 | my $g = ML::TensorFlow::CAPI::TF_NewGraph(); 128 | return bless(\$g => $class); 129 | } 130 | 131 | sub new_from_graph_def { 132 | my ($class, $buffer, $opt) = @_; 133 | 134 | my $graph = ML::TensorFlow::Graph->new; 135 | my $import_opt = ML::TensorFlow::_ImportGraphDefOptions->new($opt); 136 | my $status = ML::TensorFlow::Status->new(); 137 | 138 | ML::TensorFlow::CAPI::TF_GraphImportGraphDef( 139 | $$graph, 140 | $$buffer, 141 | $$import_opt, 142 | $$status 143 | ); 144 | 145 | if (not $status->is_ok) { 146 | die("Failed to create new TF graph from graphdef: " . $status->get_message); 147 | } 148 | 149 | return $graph; 150 | } 151 | 152 | sub DESTROY { 153 | my ($self) = @_; 154 | ML::TensorFlow::CAPI::TF_DeleteGraph($$self); 155 | } 156 | }; # end Graph 157 | 158 | 159 | package ML::TensorFlow::Session { 160 | sub new { 161 | my ($class, $graph, $sessopt) = @_; 162 | 163 | if (!Scalar::Util::blessed($graph) || !$graph->isa("ML::TensorFlow::Graph")) { 164 | Carp::croak("Need a Graph object"); 165 | } 166 | 167 | if (!Scalar::Util::blessed($sessopt) || !$sessopt->isa("ML::TensorFlow::SessionOptions")) { 168 | Carp::croak("Need a SessionOptions object"); 169 | } 170 | 171 | my $status = ML::TensorFlow::Status->new; 172 | my $s = ML::TensorFlow::CAPI::TF_NewSession($$graph, $$sessopt, $$status); 173 | if (not $status->is_ok) { 174 | die("Failed to create new TF session: " . $status->get_message); 175 | } 176 | 177 | my $self = bless(\$s => $class); 178 | return $self; 179 | } 180 | 181 | sub DESTROY { 182 | my ($self) = @_; 183 | my $status = ML::TensorFlow::Status->new; # Mocking up status FIXME, what if that's not TF_OK? 184 | ML::TensorFlow::CAPI::TF_DeleteSession($$self, $$status); 185 | return; 186 | } 187 | 188 | sub close { 189 | my ($self) = @_; 190 | my $status = ML::TensorFlow::Status->new; 191 | ML::TensorFlow::CAPI::TF_CloseSession($$self, $$status); 192 | return $status; 193 | } 194 | 195 | }; # end Session 196 | 197 | 198 | 199 | package ML::TensorFlow::Tensor { 200 | 201 | my $dealloc_closure = sub {}; # memory managed by Perl (?) 202 | my $ffi_closure = $ML::TensorFlow::CAPI::FFI->closure($dealloc_closure); 203 | #my $opaque_closure = $ML::TensorFlow::CAPI::FFI->cast(tensor_dealloc_closure_t => 'opaque', $ffi_closure); 204 | 205 | sub new { 206 | my ($class, $type, $dims, $datablob) = @_; 207 | 208 | my $s = ML::TensorFlow::CAPI::TF_NewTensor( 209 | $type, 210 | $dims, scalar(@$dims), 211 | $datablob, bytes::length($datablob), 212 | $ffi_closure, 0 # as in: NULL 213 | ); 214 | 215 | my $self = bless(\$s => $class); 216 | return $self; 217 | } 218 | 219 | sub DESTROY { 220 | my ($self) = @_; 221 | ML::TensorFlow::CAPI::TF_DeleteTensor($$self); 222 | } 223 | 224 | sub get_type { 225 | my ($self) = @_; 226 | return ML::TensorFlow::CAPI::TF_TensorType($$self); 227 | } 228 | 229 | sub get_num_dims { 230 | my ($self) = @_; 231 | return ML::TensorFlow::CAPI::TF_NumDims($$self); 232 | } 233 | 234 | sub get_dim_size { 235 | my ($self, $dim_index) = @_; 236 | return ML::TensorFlow::CAPI::TF_Dim($$self, $dim_index); 237 | } 238 | 239 | sub get_tensor_byte_size { 240 | my ($self) = @_; 241 | return ML::TensorFlow::CAPI::TF_TensorByteSize($$self); 242 | } 243 | 244 | sub get_tensordata { 245 | my ($self) = @_; 246 | my $bytes = ML::TensorFlow::CAPI::TF_TensorByteSize($$self); 247 | return ML::TensorFlow::CAPI::_make_perl_string_copy_from_opaque_string( 248 | ML::TensorFlow::CAPI::TF_TensorData($$self), 249 | $bytes 250 | ); 251 | } 252 | }; # end Tensor 253 | 254 | 255 | 1; 256 | 257 | __END__ 258 | 259 | =head1 NAME 260 | 261 | ML::TensorFlow - ... 262 | 263 | =head1 SYNOPSIS 264 | 265 | use ML::TensorFlow; 266 | 267 | =head1 DESCRIPTION 268 | 269 | =head2 EXPORT 270 | 271 | =head1 FUNCTIONS 272 | 273 | =head1 SEE ALSO 274 | 275 | =head1 AUTHOR 276 | 277 | Steffen Mueller, Esmueller@cpan.orgE 278 | 279 | =head1 COPYRIGHT AND LICENSE 280 | 281 | Copyright (C) 2016 by Steffen Mueller 282 | 283 | This library is free software; you can redistribute it and/or modify 284 | it under the same terms as Perl itself, either Perl version 5.8.0 or, 285 | at your option, any later version of Perl 5 you may have available. 286 | 287 | =cut 288 | 289 | -------------------------------------------------------------------------------- /lib/ML/TensorFlow/CAPI.pm: -------------------------------------------------------------------------------- 1 | package ML::TensorFlow::CAPI; 2 | use 5.14.2; 3 | use warnings; 4 | 5 | our $VERSION = '1.01'; 6 | 7 | require ML::TensorFlow; 8 | use FFI::Platypus; 9 | use FFI::CheckLib (); 10 | use FFI::Platypus::Record; 11 | 12 | use Exporter 'import'; 13 | our @EXPORT_OK; 14 | our %EXPORT_TAGS = (all => \@EXPORT_OK); 15 | 16 | my %TF_Code_Enum; 17 | my %TF_DataType_Enum; 18 | 19 | BEGIN: { 20 | %TF_DataType_Enum = ( 21 | TF_FLOAT => 1, 22 | TF_DOUBLE => 2, 23 | TF_INT32 => 3, # Int32 tensors are always in 'host' memory. 24 | TF_UINT8 => 4, 25 | TF_INT16 => 5, 26 | TF_INT8 => 6, 27 | TF_STRING => 7, 28 | TF_COMPLEX64 => 8, # Single-precision complex 29 | TF_COMPLEX => 8, # Old identifier kept for API backwards compatibility 30 | TF_INT64 => 9, 31 | TF_BOOL => 10, 32 | TF_QINT8 => 11, # Quantized int8 33 | TF_QUINT8 => 12, # Quantized uint8 34 | TF_QINT32 => 13, # Quantized int32 35 | TF_BFLOAT16 => 14, # Float32 truncated to 16 bits. Only for cast ops. 36 | TF_QINT16 => 15, # Quantized int16 37 | TF_QUINT16 => 16, # Quantized uint16 38 | TF_UINT16 => 17, 39 | TF_COMPLEX128 => 18, # Double-precision complex 40 | TF_HALF => 19, 41 | TF_RESOURCE => 20, 42 | ); 43 | 44 | %TF_Code_Enum = ( 45 | TF_OK => 0, 46 | TF_CANCELLED => 1, 47 | TF_UNKNOWN => 2, 48 | TF_INVALID_ARGUMENT => 3, 49 | TF_DEADLINE_EXCEEDED => 4, 50 | TF_NOT_FOUND => 5, 51 | TF_ALREADY_EXISTS => 6, 52 | TF_PERMISSION_DENIED => 7, 53 | TF_UNAUTHENTICATED => 16, 54 | TF_RESOURCE_EXHAUSTED => 8, 55 | TF_FAILED_PRECONDITION => 9, 56 | TF_ABORTED => 10, 57 | TF_OUT_OF_RANGE => 11, 58 | TF_UNIMPLEMENTED => 12, 59 | TF_INTERNAL => 13, 60 | TF_UNAVAILABLE => 14, 61 | TF_DATA_LOSS => 15, 62 | ); 63 | 64 | push @EXPORT_OK, qw(TF_DataType_Enum TF_Code_Enum); 65 | } # end BEGIN 66 | 67 | use constant (TF_DataType_Enum => \%TF_DataType_Enum); 68 | use constant (TF_Code_Enum => \%TF_Code_Enum); 69 | 70 | 71 | # Global Init 72 | our $FFI = FFI::Platypus->new; 73 | $FFI->lib(FFI::CheckLib::find_lib_or_exit(lib => 'tensorflow')); 74 | 75 | 76 | 77 | # just some named types for the FFI to make it more readable 78 | my $TF_Status_Ptr = "opaque"; 79 | my $TF_Tensor_Ptr = "opaque"; 80 | my $TF_SessionOptions_Ptr = "opaque"; 81 | my $TF_Session_Ptr = "opaque"; 82 | my $TF_Buffer_Ptr = "opaque"; 83 | my $TF_Graph_Ptr = "opaque"; 84 | my $TF_Library_Ptr = "opaque"; 85 | my $TF_ImportGraphDefOptions_Ptr = "opaque"; 86 | 87 | my $TF_Code_Enum_t = "int"; 88 | my $TF_DataType_Enum_t = "int"; 89 | 90 | use constant TF_BUFFER_SIZE => ML::TensorFlow::Buffer::_get_struct_size(); 91 | my $TF_Buffer_Opaque = "record(" . TF_BUFFER_SIZE . ")"; 92 | 93 | # Status API 94 | $FFI->attach( "TF_NewStatus", [] => $TF_Status_Ptr ); 95 | $FFI->attach( "TF_DeleteStatus", [$TF_Status_Ptr] => "void" ); 96 | $FFI->attach( "TF_SetStatus", [$TF_Status_Ptr, $TF_Code_Enum_t, "string"] => "void" ); 97 | $FFI->attach( "TF_GetCode", [$TF_Status_Ptr] => $TF_Code_Enum_t ); 98 | $FFI->attach( "TF_Message", [$TF_Status_Ptr] => "string" ); 99 | 100 | 101 | # Buffer API 102 | # See TensorFLow.xs 103 | #$FFI->attach( "TF_NewBufferFromString", ['string', 'size_t'] => $TF_Buffer_Ptr ); 104 | #$FFI->attach( "TF_NewBuffer", [] => $TF_Buffer_Ptr ); 105 | #$FFI->attach( "TF_DeleteBuffer", [$TF_Buffer_Ptr] => 'void' ); 106 | 107 | # WTF:? 108 | #extern TF_Buffer TF_GetBuffer(TF_Buffer* buffer); 109 | #typedef struct { 110 | # const void* data; 111 | # size_t length; 112 | # void (*data_deallocator)(void* data, size_t length); 113 | #} TF_Buffer; 114 | 115 | 116 | # Tensor API 117 | #TF_DataType, const int64_t* dims, int num_dims, 118 | # void* data, size_t len, 119 | # void (*deallocator)(void* data, size_t len, 120 | # void* arg), 121 | # void* deallocator_arg); 122 | #exports.TF_Destructor = ffi.Callback('void', ['void*', 'size_t', 'void*'], function(data, len, arg) {}); 123 | 124 | $FFI->type('(string,size_t,opaque)->void' => 'tensor_dealloc_closure_t'); 125 | 126 | $FFI->attach( 127 | 'TF_NewTensor', 128 | [ 129 | $TF_DataType_Enum_t, 130 | 'sint64[]', 'int', # dim sizes, ndims 131 | 'string', 'size_t', # data, data len in bytes 132 | 'tensor_dealloc_closure_t', 'opaque', # deallocator callback, deallocator arg 133 | ], 134 | $TF_Tensor_Ptr 135 | ); 136 | 137 | $FFI->attach( 'TF_AllocateTensor', [$TF_DataType_Enum_t, 'sint64[]', 'int', 'size_t'] => $TF_Tensor_Ptr ); 138 | $FFI->attach( 'TF_DeleteTensor', [$TF_Tensor_Ptr] => 'void' ); 139 | $FFI->attach( 'TF_TensorType', [$TF_Tensor_Ptr] => $TF_DataType_Enum_t ); 140 | $FFI->attach( 'TF_NumDims', [$TF_Tensor_Ptr] => 'int' ); 141 | $FFI->attach( 'TF_Dim', [$TF_Tensor_Ptr, 'int'] => 'sint64' ); 142 | $FFI->attach( 'TF_TensorByteSize', [$TF_Tensor_Ptr] => 'size_t' ); 143 | 144 | # warning: no encapsulation whatsoever to this one... 145 | $FFI->attach( 'TF_TensorData', [$TF_Tensor_Ptr] => 'opaque'); 146 | 147 | # String-encode/decode stuff 148 | # TODO 149 | #extern size_t TF_StringEncode(const char* src, size_t src_len, char* dst, 150 | # size_t dst_len, TF_Status* status); 151 | #extern size_t TF_StringDecode(const char* src, size_t src_len, const char** dst, 152 | # size_t* dst_len, TF_Status* status); 153 | #extern size_t TF_StringEncodedSize(size_t len); 154 | 155 | 156 | # SessionOptions API 157 | $FFI->attach( 'TF_NewSessionOptions', [] => $TF_SessionOptions_Ptr ); 158 | $FFI->attach( 'TF_SetTarget', [$TF_SessionOptions_Ptr, 'string'] => 'void' ); 159 | #$FFI->attach( 'TF_SetConfig', [$TF_SessionOptions_Ptr, 'void*', 'size_t', $TF_Status_Ptr] => 'void' ); 160 | $FFI->attach( 'TF_SetConfig', [$TF_SessionOptions_Ptr, 'string', 'size_t', $TF_Status_Ptr] => 'void' ); 161 | $FFI->attach( 'TF_DeleteSessionOptions', [$TF_SessionOptions_Ptr] => 'void' ); 162 | 163 | 164 | # Graph API 165 | $FFI->attach( 'TF_NewGraph', [] => $TF_Graph_Ptr); 166 | $FFI->attach( 'TF_DeleteGraph', [$TF_Graph_Ptr] => 'void'); 167 | 168 | # ImportGraphDef(Options) API 169 | $FFI->attach( 'TF_NewImportGraphDefOptions', [] => $TF_ImportGraphDefOptions_Ptr ); 170 | $FFI->attach( 'TF_DeleteImportGraphDefOptions', [$TF_ImportGraphDefOptions_Ptr] => 'void' ); 171 | $FFI->attach( 'TF_ImportGraphDefOptionsSetPrefix', [$TF_ImportGraphDefOptions_Ptr, 'string'] => 'void' ); 172 | $FFI->attach( 'TF_GraphImportGraphDef', 173 | [ $TF_Graph_Ptr, 174 | $TF_Buffer_Ptr, 175 | $TF_ImportGraphDefOptions_Ptr, 176 | $TF_Status_Ptr ] 177 | => 'void' ); 178 | 179 | # TODO the whole graph/operation shebang 180 | 181 | 182 | # Session API 183 | # FIXME this seems to leak deep inside tensorflow. 184 | $FFI->attach( 'TF_NewSession', [$TF_Graph_Ptr, $TF_SessionOptions_Ptr, $TF_Status_Ptr] => $TF_Session_Ptr ); 185 | $FFI->attach( 'TF_CloseSession', [$TF_Session_Ptr, $TF_Status_Ptr] => 'void' ); 186 | $FFI->attach( 'TF_DeleteSession', [$TF_Session_Ptr, $TF_Status_Ptr] => 'void' ); 187 | 188 | 189 | # Running things API 190 | # TODO 191 | # extern void TF_SessionRun(TF_Session* session, 192 | # // RunOptions 193 | # const TF_Buffer* run_options, 194 | # // Input tensors 195 | # const TF_Output* inputs, 196 | # TF_Tensor* const* input_values, int ninputs, 197 | # // Output tensors 198 | # const TF_Output* outputs, TF_Tensor** output_values, 199 | # int noutputs, 200 | # // Target operations 201 | # const TF_Operation* const* target_opers, int ntargets, 202 | # // RunMetadata 203 | # TF_Buffer* run_metadata, 204 | # // Output status 205 | # TF_Status*); 206 | 207 | # There's also TF_SessionPRunSetup,TF_SessionPRun, which is marked as experimental 208 | 209 | # TF_Library API 210 | $FFI->attach( "TF_LoadLibrary", ['string', $TF_Status_Ptr] => $TF_Library_Ptr ); 211 | # returning straight up buffer (not ptr) 212 | $FFI->attach( "TF_GetOpList", [$TF_Library_Ptr] => $TF_Buffer_Opaque ); 213 | $FFI->attach( "TF_DeleteLibraryHandle", [$TF_Library_Ptr] => 'void' ); 214 | $FFI->attach( "TF_GetAllOpList", [] => $TF_Buffer_Ptr ); 215 | 216 | 217 | 1; 218 | 219 | __END__ 220 | 221 | =head1 NAME 222 | 223 | ML::TensorFlow::CAPI - Internal CAPI wrapper 224 | 225 | =head1 SYNOPSIS 226 | 227 | use ML::TensorFlow; 228 | 229 | =head1 DESCRIPTION 230 | 231 | =head2 EXPORT 232 | 233 | =head1 FUNCTIONS 234 | 235 | =head1 SEE ALSO 236 | 237 | =head1 AUTHOR 238 | 239 | Steffen Mueller, Esmueller@cpan.orgE 240 | 241 | =head1 COPYRIGHT AND LICENSE 242 | 243 | Copyright (C) 2016 by Steffen Mueller 244 | 245 | This library is free software; you can redistribute it and/or modify 246 | it under the same terms as Perl itself, either Perl version 5.8.0 or, 247 | at your option, any later version of Perl 5 you may have available. 248 | 249 | =cut 250 | 251 | -------------------------------------------------------------------------------- /t/00_load.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More tests => 1; 4 | use_ok("ML::TensorFlow"); 5 | 6 | -------------------------------------------------------------------------------- /t/01_status.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More tests => 5; 4 | use ML::TensorFlow qw(:all); 5 | 6 | my $s = Status->new; 7 | isa_ok($s, "ML::TensorFlow::Status"); 8 | 9 | is($s->get_code, TF_Code_Enum()->{TF_OK}, "Initialized to TF_OK"); 10 | is($s->get_message, "", "Initialized to empty string"); 11 | 12 | $s->set_status(TF_Code_Enum()->{TF_CANCELLED}, "cancel"); 13 | 14 | is($s->get_code, TF_Code_Enum()->{TF_CANCELLED}, "Setting code works"); 15 | is($s->get_message, "cancel", "Setting msg works"); 16 | 17 | -------------------------------------------------------------------------------- /t/02_misc_basic_constructors.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More tests => 4; 4 | use ML::TensorFlow qw(:all); 5 | 6 | SCOPE: { 7 | my $sessopt = SessionOptions->new; 8 | isa_ok($sessopt, "ML::TensorFlow::SessionOptions"); 9 | 10 | my $graph = Graph->new; 11 | isa_ok($graph, "ML::TensorFlow::Graph"); 12 | 13 | my $session = Session->new($graph, $sessopt); 14 | isa_ok($session, "ML::TensorFlow::Session"); 15 | } # end SCOPE 16 | 17 | pass("Alive at end"); 18 | 19 | -------------------------------------------------------------------------------- /t/03_buffer.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More tests => 11; 4 | use ML::TensorFlow qw(:all); 5 | 6 | SCOPE: { 7 | my $buffer = Buffer->new; 8 | isa_ok($buffer, "ML::TensorFlow::Buffer"); 9 | } 10 | pass("Alive after empty buffer DESTROY"); 11 | 12 | SCOPE: { 13 | my $buffer = Buffer->new("foo"); 14 | isa_ok($buffer, "ML::TensorFlow::Buffer"); 15 | is($buffer->get_data_copy, "foo", "Buffer->get_data_copy"); 16 | is($buffer->get_data_view, "foo", "Buffer->get_data_view"); 17 | } 18 | pass("Alive after non-empty buffer DESTROY"); 19 | 20 | SCOPE: { 21 | my $buffer = Buffer->new("foo"); 22 | 23 | is($buffer->get_data_copy, "foo", "Buffer->get_data_copy"); 24 | is($buffer->get_data_view, "foo", "Buffer->get_data_view"); 25 | 26 | my $new_data = "barbazbuz"; 27 | $buffer->set_data($new_data); 28 | is($buffer->get_data_copy, "$new_data", "Buffer->get_data_copy after setting data"); 29 | is($buffer->get_data_view, "$new_data", "Buffer->get_data_view after setting data"); 30 | } 31 | 32 | pass("Alive at end"); 33 | 34 | -------------------------------------------------------------------------------- /t/05_tensor.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More tests => 7; 4 | use ML::TensorFlow qw(:all); 5 | use Config qw(%Config); 6 | 7 | SCOPE: { 8 | my $bytes = $Config{doublesize} * 6; 9 | my $blob = "\0" x $bytes; 10 | my $tensor = Tensor->new( 11 | TF_DataType_Enum()->{TF_DOUBLE}, 12 | [2,3], 13 | $blob 14 | ); 15 | 16 | isa_ok($tensor, "ML::TensorFlow::Tensor"); 17 | is($tensor->get_type, TF_DataType_Enum()->{TF_DOUBLE}, "Tensor of right type"); 18 | is($tensor->get_num_dims, 2, "Right number of dims"); 19 | is($tensor->get_dim_size(0), 2, "Dim 0 of right size"); 20 | is($tensor->get_dim_size(1), 3, "Dim 1 of right size"); 21 | is($tensor->get_tensordata, $blob, "Blob unchanged"); # sigh 22 | } 23 | 24 | pass("Alive at end"); 25 | 26 | -------------------------------------------------------------------------------- /typemap: -------------------------------------------------------------------------------- 1 | # from "perlobject.map" Dean Roehrich, version 19960302 2 | 3 | # O_OBJECT -> link an opaque C or C++ object to a blessed Perl object. 4 | 5 | TYPEMAP 6 | 7 | TF_Buffer* O_OBJECT 8 | 9 | 10 | ###################################################################### 11 | OUTPUT 12 | 13 | # The Perl object is blessed into 'CLASS', which should be a 14 | # char* having the name of the package for the blessing. 15 | O_OBJECT 16 | sv_setref_pv( $arg, CLASS, (void*)$var ); 17 | 18 | ###################################################################### 19 | INPUT 20 | 21 | O_OBJECT 22 | if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) 23 | $var = ($type)SvIV((SV*)SvRV( $arg )); 24 | else{ 25 | warn( \"${Package}::$func_name() -- $var is not a blessed SV reference\" ); 26 | XSRETURN_UNDEF; 27 | } 28 | 29 | --------------------------------------------------------------------------------