
Wolfram Language's Foreign Function Interface lets you call C libraries directly from top-level code without writing C or compiling anything. The demo loads OpenSSL for cryptography, uses GEOS for geometry, and shows how to pass Wolfram functions to C code and manage memory with managed objects to prevent leaks.
Chapters
Started. All right, well, so today I'm going to be talking about. Oh, or yeah, today I'm going to be talking a bit about FFI, the foreign function interface, which we added in version 13.3. So essentially, FFI is a way of connecting to added dynamic libraries, specifically the compatible dynamic libraries. Now, there are a few different existing. A few different existing systems that we have for connecting to dynamic libraries. So in particular there's library Link, which has been around for a while, and there is the compiler through function compile and through library function declaration, which also has a way of calling functions from external libraries. Each of those has its own kind of uses. But the main differences with FFI are that it essentially lets you do a lot more stuff in top level code. So for example, with Library Link, Library Link lets you load functions from libraries, but only functions with very, very specific signatures that basically signatures that are specific to Wolfram language and to Library Link. So realistically, what that means is if you have some library that you'd like to connect to, that you have to compile an interface library that sits between your target library and the kernel, and which is just sort of a library link interface between them, which realistically also means you're writing C code or something like it to build that library. So. Right. So the advantages of FFI is that instead it lets you do all that interface work in Wolfram language, as opposed to in something like C, which is both nice because it means you don't have to write as much C, you don't have to manage all these interface files and compiling them and loading them and so on. So just removes entire layer of complexity. But also because it means that you don't have to do compilation. So essentially you can both. Right, it means that if you're for example, building a packlet that relies on an external library, with Library Link, you'd have to compile a kind of interface library. You'd be building an interface library for all the platforms you care about and so on. But with FFI you don't need to do that. Instead you just. As long as you have the library, you don't need to compile anything else. Right? And again, everything can be done in the top level. There's no need to write C or anything like that. So let's see an example of how this actually works. So here I've got. There's a library that gets included in Wolfram language builds. I think it's called Compiler Demo Base, and I can use something like Find Library to see where that is. So yeah, you can see there's this library compiler, Demobase Dylib, and in that library there's a function called add1, which takes an integer and returns an integer and you can guess what it does. So if I want to connect to this with ffi, it's pretty simple. So I write foreign function load, I give it the name of the library compiler demobase and the name of the function add 1, and then I need to give it the type signature of the function, which you can do using the compilers, using basically the compiler's type language. There's lots of documentation for how this works, but this is the same as what works with, for example, function declaration and things like that. We've talked a little bit about maybe supporting more things here, so maybe you'll be able to put just this string or something like that, just to make it even easier. So you don't need to have as much compiler experience. But that's a future thing, maybe. So if I run this right, so it actually went and it loaded this library and it loaded the add one function from it. So I'm going to maybe save this in a variable. And now if I want to try calling this function, I can just apply that foreign function to some arguments. So add one of 12 and it returns 13. So yeah, so already you can see it's just like one line of code and we're calling some random function from a library we found, which is pretty nice. And you can see this is also pretty efficient. So if I look at the repeated timing of this, it's on the order of a millionth of a second to run this call here. So, yeah, often what that means is that most of the, most of the time spent is. I mean, this is close to basically the speed of light of the evaluator, if you know what I mean. So basically when you have a big program that uses this, the time spent is all in the other top level code around. The FFI calls, the FFI calls themselves are effectively instant for the purposes of profiling, usually. So, yeah, so, okay, so that's, that's sort of a toy example, but let's maybe go to something a little more real. So here there's a library called OpenSSL, which is a very popular library that's, that's used for, among other things, used for cryptography. This is kind of the definitive cryptography library. And OpenSSL is actually included in builds of form language because it's used for functions like encrypt, decrypt, hash, et cetera. So What I'm gonna do here is, this is a piece of. This is. I should, maybe I should iconize this or something. But basically that's a piece of code that gets you the path to the libcrypto executable that you need in order to run a bunch of OpenSSL server stuff. So, yeah, so there's a few functions in OpenSL that we're going to look at. So the first one is very simple. It's this1, open SSL version num. This is pretty similar to the add1 function that we loaded before. But we can try loading this. So foreign function load, give it the path to OpenSSL. The function is called OpenSSL version num. And then the type signature is. It takes no arguments and returns a C unsigned long. So call that get open sslversion. Now if I say getopenslversion open close, it returns this number, which I guess represents the version of OpenSSL that we're running. So, okay, so we can see that we're calling real functions from real library here. But now let's maybe call a more useful one. So this function, rand bytes, is open SSL's cryptographically secure pseudorandom number generator. So this is what's used to generate things like encryption keys and stuff like that, I believe. So we can try calling this. So it's making the foreign function is just as easy. So we'll just. Maybe I'll just copy this down though, so we can kind of use it a bit. So the function is called RAND bytes and it takes in this case a pointer to an unsigned char. It really takes a buffer, so it takes an array of unsigned chars and then the length of the array and it will populate that array. So I'll write something like raw pointer to a C unsigned char. Maybe I should format this a little bit to make it more clear. And then it takes a cint and the whole thing returns another cint. So I can run that. And we've loaded the function, but now we've sort of run into an issue because now it's not clear. Well, we need to have this buffer, for example, in order to run this function, we need to allocate some memory and then let this function populate it. But there isn't normally any, you know, what do we actually pass to this thing? So the answer is there are a bunch of different support functions that exist now around FFI that let you basically create these inputs and interpret the outputs. So one of the main sets of them are the so called raw memory Functions, which are basically functions for allocating, freeing, reading, writing, importing, exporting, raw memory. So, for example, here what I might say is, I'll say something like buf equals, we can say raw memory allocate, and we'll give it the type. So in this case it's a buffer of C unsigned chars that allocated a single one. Maybe now I'll allocate, I don't know, 100, just to pick a number. So now we have a memory managed buffer of 100 chars. We'll talk about the memory management a bit later, but for now it'll just magically work. And now I can try feeding this to our RAND bytes function. So I'll give it the buffer in the first argument, and then the second argument is the length of the buffer, which in this case is 100. And I can run that and we get it returns the error code 1, which usually is a good sign. And now we can read back what was in the buffer. So for that I can use raw memory import, and I can import the buffer and I'll import it as a byte array of length 100. And so we get back this length 100 byte array. Actually, maybe I'll do list instead, just so that we can see the actual bytes. So that's a bunch of bytes. If I run the RAND bytes function again. So this should repopulate the array with new random bytes. And I run the import again. You can see the bytes have changed. You know, whether. You know, you can guess that they're. Whether or not they're random. But. Yeah, and now maybe just to show how you might do this, we can try packaging this all up into a nice function. So maybe I'll make a function called RAND bytes. It takes an N, which is the number of bytes to generate, and we'll just basically copy our code from up here. So we have, first we're going to allocate the buffer with length N. Then we're going to call RAND bytes on that buffer, and then we're going to import the result and we'll. We'll do it as a byte array for the actual function. Now if I say rand bytes of 10, we get 10 random bytes. Maybe I'll say normal there. If I say 1,000, we get a thousand random bytes. And, you know, I could do, you know, generate a megabyte, generate 100 megabytes and so on. And you can see it's pretty fast. And also it's worth noting that there should be sort of automatic memory management here. So that we're not leaking this memory each time we run it, it is freeing this buffer at the end of the module and so on. So yeah, ok, so that sort of shows you how you might connect to a real function out in the wild. And again it's notable that we didn't have to leave the notebook, we didn't have to write anything other than just top level Wolfram language code to get that to work. So let's maybe move on to another one that's sort of similar but shows some slightly different things. So this is another function from Open SSL called SHA256 and it essentially computes the SHA256 hash of a sequence of bytes. So this is going to be vaguely similar. So let's first load the foreign function. So again it's from OpenSSL and the function is called SHA256. And for the type signature it takes first a raw pointer to a cunsigned character, then it takes a csizet, then it takes another raw pointer to a C unsigned char and then it returns a raw pointer to a C unsigned char. Okay, so now we've, we've loaded that function. So the way this one works is again, it takes a buffer of the input data. It takes an input that tells it how long the input data is and then it will populate this buffer with 256 bits of data. So that's what, 32 bytes? Right. So let's see. So again, the first thing we have to do here is generate the inputs. So in this case let's just assume we're taking the hash of a string. Although it would be just as easy to do a byte array. So I'll write a string like, I don't know, hello world and then we need to turn that into a buffer. So there's a, there's a function, raw memory export, and I can give it, for example, this string and it will turn that into a managed. It'll automatically allocate and then populate it with that, that string there. Maybe we'll call this plaintext buf. And then we need to allocate a place to write the ciphertext, the hash. So we'll call that ciphertext buf and this time it's just raw memory allocate. Actually, I'm going to do, let me see here. Yeah, actually, so I'm going to do, instead of C unsigned char, I'm going to do unsigned integer 8, which is on this platform the same thing, but on some platforms could theoretically be platform dependent. Although I think chars are always eight bytes or eight bits. But okay, so we'll do it like this. So I'll get 32 unsigned integer eights, that's for the result. And then I can now try running this. So I can try giving it the buffer or sorry, the plain text buffer. I can give it the length of the plaintext buffer which is approximately string length and then I can give it the ciphertext and I can run that and I assume that it's now populated it with the hashtag. And now I can run raw memory import and we can import that ciphertext in basically the same way we did before and get back a bunch of bytes. Now we can check this by comparing it against the built in hash function. So we'll hash the same input with SHA256 and I think I can ask for the byte array back and then I'll say normal. And yeah, you can see that this is the same sequence of bytes. So again, calling a different function that takes even more complicated input and outputs. But it was really only just, really just three lines of code to do the actual call and one line to declare the function. So again, it's pretty simple and it doesn't involve nearly as much kind of going outside the languages as, as with, for example, Ibry link, so maybe another, another kind of input. So in those examples a lot of the inputs were that we had to like. A lot of the complexity came around, not the actual calling of the function itself, but the fact that we needed to generate inputs for the function and then also interpret the outputs which might be represented in some binary format that's very C specific and not, not very, you know, familiar to Wolfram language typically. So another kind of thing that, that you can often pass to C functions are, are function pointers. So essentially you can pass functions to other functions. And just as we have ways of turning arrays of numbers basically into, and strings into, into binary, you know, to this, these raw memory representations, we also have ways of representing Wolfram language functions as, as function pointers first. There, let me see, there was a question in the chat. Does this work to translate modeling code from something like Python or Snap Pint Wolfram language? So right, so this, all this FFI stuff is specific to dynamic libraries. So right, so different, different languages have different ways to connect to other languages and to open language in particular. So for something like Python, because it's an interpreted language, it can't generate dynamic libraries, at least not, not usually. And so instead the way you connect to it is through external evaluate. So there's. I won't go too into it, but. But I'm not sure I have it set up on my computer right now. But generally speaking, you can do things like this. Oh gosh, I have to. Can I. Will that work? Yeah. So there's an example where I just ran a Python program from within Wolfram language. So if you have a bunch of Python code, this is how you would connect to it. So the distinction is that basically most compiled languages have a way of generating dynamic libraries so that you can link them into C and into other languages. But. Right, and that's what F of I is for. So, okay, so let's see. So here's an example of. I was talking about functions that take other functions. So here's an example of a fairly simple C program that it has. It includes a function that itself takes another function. So I don't know how familiar everyone is with C, but this is the somewhat obscure the function pointer syntax. So this means that we'll have a. Basically there's a function called arraymaker and it itself takes a function that takes along and returns along, which is a 64 bit number usually. And then all it's going to do is it's going to call that function a bunch of times on numbers 0 through 9 and it's going to populate some array and it technically has a memory leak, but whatever. So I'll just run that here. So, okay, we've compiled that. That library. Let's see here. So again, we can easily load the function, so foreign function load and then we'll give it path to the library. And the function was called arraymaker. And now the first thing it took is this function pointer. For now we're just gonna write opaque raw pointer, which is basically just a void star. It's basically saying there's a pointer here, but we're not gonna say anything about what it really represents. And then it returns a raw pointer to a fee long and there it is. So now I'll call this maybe arraymaker C. But again we run into the problem of, well, how do we generate this pointer that we feed to this in order to use it? And the answer is there's this foreign callback mechanism and in particular we can use create foreign callback. So essentially what this does is it takes two arguments. The first argument is a function and the second argument is the type signature. So and the first argument could be any Wolfram language function. So here I'm just going to write something like, you know, maybe echo Evaluation of hash squared, for example. So echo evaluation basically just prints out. Oh, gosh. Oh, it's very, very small. Let me see if I have a way of maybe getting that to be bigger or I'll just. Let's see. I can do that. And now you can hopefully see it. So this is what it, it printed out. It printed out that it was about to evaluate two plus two and that that evaluated to four. So in this case I'm going to. This has to be a function that takes along and returns along. So I'm going to just write something like it's just going to take the number and square it and we're going to print out both the input and the output and then we have to give it the type. So again, it takes along and it returns along. And this gives back this foreign callback object. So I'll maybe call this a callback. And now I can pass this function, this callback function into the foreign function, the one that expects the function pointer. And it should start. Yeah, it should start printing out all the inputs and outputs because again, the. Every time it calls the callback it's going to print. So essentially what's happening here is we have an external library that's calling a Wolfram language function from C, which is pretty cool. So here again, it's just running on numbers, you know, 0 through 9 and it's squaring them and then we also get back the output. We could also maybe just quickly import that. So I think it was just length 10. Right. And there we go. And there are all the results of the callback being called, which is all that arraymaker function did, but. Right. So I mean, this sort of shows how you can essentially make Wolfram language code accessible to C code, which is both. A few places where that's useful. It's useful in. I think I saw an example with an SQL library. I think it might have been SQLite where you could define custom callbacks in certain situations that as it's running the query, it will call Wolfram language code in the process of running its query. So you can really like intermesh the Wolfram language code with the C code. Another common case when calling libraries is a lot of libraries have essentially error handling callbacks. So if an exception, if something goes horribly wrong, it'll call this function to report the error. So in that way you can basically integrate the library's error handling with Wolfram language error handling, which can be pretty useful. Let's see. So maybe one other kind of common situation that you run into with dealing with external libraries is that most of the time C libraries don't do memory management, or at least they expect you to do the memory management. So that is, if you have to allocate some object, you then are responsible for freeing it. Now, this is something that normally you don't have to worry about in Wolfram language, because Wolfram language uses automatic reference counting and it will automatically free an expression if it's no longer referenced. So essentially, managed object is a new symbol that lets you take advantage of the kernel's existing reference counting stuff to basically reference count arbitrary expressions, and in particular stuff that you get back from external libraries. So let's just make an example. So actually first I'm going to run, I'm going to set my dollar history length to zero. This is a trick that some of you may be familiar with. Basically, when you run something in Wolfram language, you can then later on percent to get the last output and you can run percent percent to get that two outputs ago and triple percent and so on. And essentially what that means for automatic reference counting is that there's always a reference to the previous output. So when you set dollar history length to zero, it essentially disables this mechanism. And it means that oh, whoops. And it means that oh, strange. And it means that you won't. There aren't these sort of hidden references to things, so it means that it'll be a little more easy to see what's going on. So in this case there's a function called Create managed expression or sorry, create CreateManagedObject. And what this does, it takes two arguments, it takes a value, and it takes a freeing function. So in this case I'm just gonna use echo as the freeing function, so it'll print when it gets freed. So in this case I'll save it in a variable. You can see we have this managed object man, I can access the value from the object just by running something like this. We get back val and critically, when there are no longer any references to the object. So if I clear the object, then it will run the freeing function. So there it ran echo of val. Again, the fact that this only freed it because my history length was zero, if it wasn't zero, it would have maybe stuck around a bit longer, but. Right. So essentially, I mean, this lets you take any object, whether for connecting to an external library or otherwise, and it lets you essentially attach reference counting to it, which is pretty useful. So for example, this is also how earlier we saw some examples with like Raw memory allocate. So if I rem allocate, say an int, you see, we get back this managed object. This is essentially equivalent to, to create managed object of allocate comma, raw memory free. Right. That's essentially what this managed object is. So. Right. That's why we didn't have to worry about freeing those allocations earlier because they were all wrapped in these managed objects that would automatically get freed when they went out of scope. So yeah, so maybe just the kind of real example of that in action. Oh, this is. Maybe I should have iconized some of this as well. So this is another library, this one is called geos and GEOS is basically a computational geometry library. So this is all just a bunch of this. This code is just for basically getting the path to the library and loading a dependency. And then we also have to run this init GEOS phase. So you have to basically initialize the library. This is actually a place where you have the opportunity to give it some function pointers, but in this case we won't. So this would be a place where you'd use foreign callback. But now there are these two other functions which are the ones we're actually interested in. So this is a function that creates a GEOS geometry object given the X and Y coordinates for a point. So again, it's a geometry library. So it has ways of representing, you know, polygons, lines, et cetera. But a lot of these are based around its point type. And this is one of the easiest functions for creating a point. So let's first start with this. So we'll say create point just like we've done before. We're just going to create this, we're going to load this foreign function for creating a point. It's going to take the GEOS library, the function name is this rather long winded thing here. And then the type signature is it's going to take two C doubles and it's going to return this GEOS geometry object. So in this case what I'm going to actually do is I'm just going to say it's an opaque RAW pointer, which is essentially like I mentioned before, is essentially just representing it as a void star in the terms. And really it's just saying it's a pointer to something and the kernel doesn't really need to know what it's a pointer to, it just needs to know it's a pointer and we'll pass it around from one library function to another. So, right, so now we have that function and Now I can run create point C, I can give it some inputs and it will allocate and create this. This point and represent it as an opaque raw pointer. And you can also see every time I run this, you see that the raw pointer object has this memory address in the front. Every time I run this, the address changes. So it's probably allocating more and more points. And in each of these cases, the points are just kind of leaking into. They're all memory leaks. Because I haven't. I'm not freeing them, I'm just creating them. There's another function called geosgeomdestroy, which is what you're supposed to call in order to free any geometry objects that you create. So let's load this one as well. So geom destroythe Again, it's just foreign function load from geos. It's called geosgeom destroy and the signature is we take an opaque raw pointer, basically this GEOS geometry object which we're representing with opaque raw pointers, and we return void, which basically means nothing. So now we have this destroy function. So now if I run, I can create a point and then I can run geom destroy on that point and we should free it. And now this will be allocating and immediately destroying the memory, which also isn't very useful. Now, the way that we can actually use this function for real is with managed objects, because managed objects let us essentially attach reference counting to different objects. So here we have this GS geometry object and we want to attach this freeing function to it. So the way I can do that is I can just say create managed object. I can give it the point, and then for the freeing function, I'll give it the destructor GEOM destroy C. Now, if I run this, we get this managed object that represents the point that will be automatically freed when it's no longer referenced. So essentially this is now a well behaved Wolfram language expression that you can create and destroy at will and it won't lead to any memory leaks. So, right, so maybe let's quickly package that into a nice function. So we'll take an X and a Y and we'll essentially just run. Just essentially run this. Now we have a function create point and we can give it some coordinates and it will give us a managed object and we can sort of be rest assured that this isn't leaking memory. Maybe we can do a quick test function memory in use, that tells you the full memory in use by the kernel. I'll maybe save this in a variable and then I'll, I'll run, maybe we'll run this a thousand times and then we'll look at before, minus or other way around, actually. And so that's telling us that basically this, it's, it's, it's not using any memory. Oh, actually, let me see. Yeah, it's not using any new memory. We could actually save this in a variable like that. And we'll see that the first time I run this, now it's using. Oh, I got it backwards again. Okay, now it's using 200,000 more bytes, so like 2,000 more kilobytes than it was before. Right. But when we threw away that output like that, now it's not using any new memory. So again, this basically means that it's automatically freeing these points when they're no longer needed. So. Right, so it's working. So we can try maybe a real example of this. There's a function in GEOS called GEOS Distance, which takes two geometry objects and a pointer to a double. And we'll basically populate that double with the distance between the two objects. So now that we have a way of creating geosgeometry objects, we can now also run operations on them like this. So we'll start by loading this function like always. So we load it from geos. The function is called geosdistant, and it takes two of these geometry objects. So it takes first an opaque raw pointer, then it takes another opaque raw pointer, then it takes the pointer that's going to be populated with the distance, and then the whole thing returns a cint, which presumably is the error code. So we'll maybe call this geostance C. So now we, we can try running this. So first we need our two points. So we'll maybe have point one. We'll maybe use our create point function, Give it one point, and point two is maybe some, some other point. So now we have our, our two points. And now we also need to generate this third out, this third input. So in this case we'll just say dist pointer equals raw memory allocate. And we just want to allocate a single C double. And now we can call the function. So we'll say geostance c and we'll give it our two points and we'll give it the distance pointer and returns error code one, which is always a good sign. And now if we say raw memory read, memory read, we get back some number looks vaguely plausible. Maybe we can look at the Euclidean distance to make sure that's the, to make sure that's right. So those are the two points. And then we'll use N and yeah, that looks like it's correct numerical result. And you can easily imagine, well, maybe let's quickly package this up into a function. So we'll say geostist takes two geometry objects and it will essentially just run. It will first. Whoops. It'll first. It'll first allocate this pointer or allocate this double into which to write the result and then it will call the function and then it will read the result. And now we have fully fledged functions for creating, creating geos geometry objects and then also running operations on them. So I can now do something like this. And so you can see this is sort of the beginning of how you would write a fully fledged connection to something like geos. Let me see, so maybe one last example. So all these examples I've been doing have been sort of self contained and sort of one function or maybe two. So one of the things that I've been sort of working on more recently is a thing called Z3 link, which is essentially using FFI to connect to Z3, which is, I think it's basically an automated theorem prover. I think Microsoft makes it right and it's available as a, a C compatible dynamic library. So I won't go too into it now, but let's see here. Right, but essentially in this case Z3 Link is just a packlet and the way it works, you know, the idea is to sort of try and generate a lot of the. Go through a lot of the idioms for using FFI in actual packlets. So the overall architecture of the packlet is there's a variable called libz3 that basically represents where the z3 library is in this case, just for development purposes, this has some kind of fake paths or paths that work on my computer. But in general this would be some path within the packlet itself so that Z3 could be bundled with the packlet and then in order to call any functions from Z3, it's just a matter of running foreign function load with those functions and then making top level wrappers for them. So in this case there are actually a few useful tricks. So one useful trick is, you can see here, I'm lazily loading this foreign function. So this is a common idiom that lets you essentially make it so that this code will be run only once when it's first used and then the result will be cached. But the point is that this won't be run when you. It won't run foreign function load until you actually run Z3 Simplify. The reason that's useful is that on some platforms foreign function load. Well, actually there's two reasons. The first reason is that on some platforms foreign function load can be a bit slow. And by slow I mean about a hundredth of a second. So in particular, like I think on, on Mac it takes about a hundredth of a second to run foreign function load, but on Linux I think it took like a millionth of a second. So my, I don't know for sure, but my guess is it has to do with code signing. That Mac OS has more kind of protections to make sure that you're not loading some crazy motious dynamic library. And that might be taking some some time. In any case, what that means is if you have a packlet like Z3 link here, which involves loading hundreds of foreign functions, you don't want to load those all when the user run needs. You want to run them when, when they actually need to be loaded, because otherwise it might take it on the order of a second to run needs, which is usually considered bad. So. Right, so that's sort of a useful trick. The other reason why it's useful is it means that you can change the user can change the, where the Z3 library is, or they can change this variable here and have it affect, have it affect everything going forward, which is useful for development, if nothing else. So just to sort of show how this, this works though. So I have a function which I won't get into, called 2Z3. So I can give it some expression like, I don't know, X squared plus, you know, ax plus B or something like that. Right. And this will essentially, this will essentially represent this as a, as a z3 Abstract Abstract Syntax tree here. This is essentially this, this z3ist object is essentially just a wrapper around a few different managed objects. So it's a wraparound which themselves are wrappers around pointers. So this is doing the same thing as in that GEOS case, except that I've added some other wrappers around it that sort of make it look a little prettier. So I can have, for example, nice summary boxes and things like that. But now I can try running functions on this. So maybe I'll save this as astrology. Now I can run something like Z3 Simplify of AST. And okay, in this case it's not a very interesting simplification, but again, the actual code to implement the Z3 Simplify was very simple. So here's the full code for Z3 Simplify. So again, all it's doing is loading this foreign function, this z3 simplify function from the dynamic library and then it's making this top level wrapper here. And essentially all this top level wrapper is doing is it's taking this AST object, it's extracting out the relevant kind of RAW pointers that are inside of it, and then it's calling the simplify function to then generate a new AST object or sorry, generate a new raw kind of AST pointer, and then we're just wrapping that again in a new AST object and getting the result. So. Right. So the point being that, I mean, all the same principles that we were looking at in the presentation, they still basically works the same way inside of a packlet and you can kind of easily scale this to fairly big connections and it works quite well. So yeah, maybe, let's see, I think that's about the end of the presentation. If there are any questions or anything, now is a good time, maybe wait a few minutes, but otherwise that should be it. So, yeah, I'll wait maybe a minute or two, see if any questions pop up. But yeah, Meanwhile I can, I can maybe show a few other nice, while people are thinking of questions, I can, I can show maybe one other nice, nice sort of idioms that you can use in these connections that sort of shown in the C3 link here. So one, one thing that's kind of common and which is used in, in Z3 link is that basically you have to the allocation of the memory management of some objects depends on others. In other words, we have these Z3 AST objects that might be memory managed, but they have to. Basically there's also a Z3 context and this context can't be freed until after all the AST objects inside of it have been sort of are done. And so the way you can do that though, you can do it very naturally with managed object by basically just including the managed object in every reference to say, the object that depends on it. So here we have this AST object, this object depends on this context being allocated and not being freed. And so all we have to do is just wrap them up together and that way every reference to the AST object will also increment the reference count of the context. So it means that you can kind of enforce these sorts of relationships very naturally. But yeah, let me see. So in form function load, is there any type checking for the arguments, slash result types, or maybe it fails during runtime? Yeah, so that's a good question. So there's, there's. Let's go back to the very first slide maybe to look at a simple example. So there are a few different places where there might be argument result checking. So there is. First of all, once you've loaded a foreign function, there is argument checking. So if I put in the wrong thing here, it will. That's. Oh, oh, oh, yes, that's. Yes, that's using a. It's sort of a trick that, that, that particular one works. Let me, Let me give it something like that. Ok, fine. Basically I think if you give it a string, if I remember right, it will automatically. It's for. It'll automatically allocate it as a buffer and then feed that. The pointer to that buffer to the function. And in this case it was interpreting the pointer as an int. And it's all a bit goofy, but it can work. But here there's real error checking where it can't work. And so in this case it gives essentially it gives a runtime error, but of course it doesn't seg fault or something like that. So in that sense there is argument checking. In the sense that there isn't, it's that this type signature here, there's nothing that ties this to. There's nothing that can be checked. So if I give the wrong thing here, if I say for example, that it takes not a cint, but it actually takes a, an integer 64, it will gladly load that as a, as a foreign function. And if you call this, you risk crashing the process. This is basically a limitation of dynamic libraries that dynamic libraries don't really include type information about what the type of the functions are. This is kind of like a 30, 40, 50 year old, maybe not 50 year old problem. That yeah, is an unfortunate feature of dynamic libraries. Maybe. But yeah, yeah, let me see. There's another question. Is it possible to use C classes? That's another very good question. So right now, not really. You can easily compile an interface library. So you can compile a C library that basically wraps all of your C functions and classes and so on, but. And then you can link to that library using foreign function load. But that defeats a lot of the advantages of foreign function load, which is that you don't have to create interface libraries in general. So this is something that we've been sort of looking a bit at and experimenting with is automatically generating those interface files so that you can essentially run something like foreign function load and point it at your C stuff and be able to call it the reason that C is complicated is that, well, among other things you have templates. So essentially a lot of C doesn't have dynamic libraries in quite the same way as C. Or at least a lot of libraries are more than just dynamic libraries. So in particular there are a lot of like header only libraries or libraries that include headers. And the reason for that is that if you have a library that includes templated functions, so say a function that can take, you know, let's say you have a vector type and your vector type can take any type, can have any kind of argument, right? It can be a vector of ints, or it can be a vector of something you've never heard of before. In order to support that, it means you actually, you can't really have a dynamic library that, that supports that vector type because you actually have to do compilation for every different, every different type of vector. And there are infinite number of vector types because you can have any type of input. And so. Right, and so basically what that means is that we, with C, if you want to say call your, call some C library from Wolfram language, you are in general going to need to do compilation at least once. And. Right, and so it's just a matter of automatically generating those interface layers, which is something that should be feasible. But there's a fair bit of kind of annoying complexity to it. But yeah, but it's something that we've been looking into a bit. So yeah, I think eventually one day it'll be possible to load C classes, but it's not possible in 13.3. So. Yeah. All right, well, unless there are any final questions, I think that should be about it. But thank you everyone for coming and thank you for some good questions and so on. And yeah, I think that should be it.
More from Guy

Cheryl Strauss Einhorn | The Human Edge: Smarter Decisions in the Age of AI | Talks at Google

Rachel Glennerster | Innovation and the Center for Global Development | Talks at Google

Rebecca Winthrop | Rethinking the Purpose of Education in the Age of AI | Talks at Google

Dr. Robert Wachter | A Giant Leap: How AI Is Transforming Healthcare... | Talks at Google






