All right, let's quiet down, please.
We need to start the next talk.
Let's give our attention to Terz, who's going to tell us
all about calling Jit compiled scripts in the context of Roto.
All right, hi, Barron.
My name is Terz, and I work for a now-net-lapse,
and I'm going to be talking to you about a little scripting language.
But before I do that, very quick disclaimer.
We kind of always have to do this at a now-net-lapse,
because there's this other thing called a now-net
that we have shums shared history with,
but we are separate entities.
And unfortunately, I cannot fund your project.
No matter how much you ask.
The thing is, they give money away, we take money,
and then with that money, we make open source software.
So, we make open source software for DNS and routing software.
So, in routing is like RPKI and BGP, if you're into networking,
we are a nonprofit organization,
and we've been around for 26 years.
Well, I say we, but I've been there for two years,
because 26 years ago I was a baby, so that doesn't quite work.
And we make loads of products,
if you have ever tinkered with DNS, you might know things like
NSV, an unbound, or some of our other products like
routinator, or rotunda, or krill, or a bunch of other things.
We have stickers.
The one that I want to talk about now is rotunda.
I wrote down it's a BGP route collector,
but I'm not assuming that you have any knowledge about networking,
so all you need to know is it's a database,
and it takes in a lot of data,
and the user needs to decide what data gets filtered.
And we want to allow them to do that with a filter language.
And that needs to be kind of fast,
because we're talking about a lot of data here.
So, my colleagues were scratching the heads around this around 2024,
and then I met up with them at Boston two years ago,
and I just finished my master's thesis in programming languages.
And, incidentally, I was looking for a job.
So, one thing from another,
I was no longer looking for a job,
and instead I was a software engineer at a Knowledge Labs
hired to do some work on this language thing that they were thinking about.
And then a little while later,
I became an organizer of the Rustweek Conference.
So, we have this thing called Rotanda,
and now we're making a language for it.
Okay, what do we call this thing, naming things as hard.
We just get a skirtle at the last part,
and we're going to stuck with Roto.
Are there any Spanish speakers here?
What does this mean?
But also broken, right?
Yeah.
Okay, I promise it's not.
Well, maybe it is a little bit.
All right, so what it is,
is it is a statically typed language that integrates really tightly with Rust.
And the strange thing is,
it gets jit compiled to machine code.
So, there's no interpreter, there's no byte code.
It's straight to machine code.
And this is what it looks like.
It looks a lot like Rust.
With some like niceties, right?
We have F strings here that you don't get in Rust.
But otherwise, you know, you all can read this, you know Rust.
So, how do we use this from Rust, right?
How do we embed this thing in our application?
What we do this?
It's just four lines.
We create a runtime.
We compile the script.
We get the function out of the script,
and then we call that function.
Now, notice that this script gets loaded at runtime of your application.
So, you can actually change the script and start your application again.
You can even reload the script if you set your application up that way.
It gives you a lot of flexibility,
but this whole compilation process happens at the runtime of your application.
Now, I didn't want this to be a tutorial about roto,
because you're all here for Rust, right?
You want to know what's going on under the hood.
So, let me tell you about that.
Roto uses this thing called crane lift.
Who sort of crane lift?
Ah, almost everybody, lovely.
For the people who don't, it is a compiler backend.
A little bit like LVM, but written purely in Rust.
So, the great thing is, you can just do cargo ad crane lift,
and now you have a compiler backend in your project.
Take some getting used to, but it is awesome.
So, roto uses crane lift.
It creates crane lift IR intermediate representation,
and then crane lift has this thing that does the rest.
It just compiles it down to machine code.
Now, what it does is it just allocates a big chunk of memory somewhere,
and it writes all the machine code there.
And then it says, yeah, you know, I've written it there.
Here's a pointer.
A pointer to the machine code you have just generated.
Okay, thanks.
Now, what do I do with that?
Well, it turns out we have to cast that pointer into a function pointer.
So, we have a slice of machine code,
and we just say, just pretend that is a function, please.
And that is what happens.
All right, so how do we make sure that this is actually sound?
Well, the first thing we need to do is check that the whatever signature
we had written in a rotoscript, that matches what we expected
on the rust side, like the function point that we are transmuting it to.
So, each argument and each return type needs to match to a rototype.
And there are some easy cases we can knock off right at the bat, right?
We can say, a bull is a bull and a UA, just a UA,
like CraneLift knows how to deal with integers of different sizes,
and it can generate the right things for us.
So, that works, right?
But then there are some more complicated types.
Right?
I'm skipping some stuff, but that's fine.
There are some more complicated stuff like option.
What does an option look like in memory?
If we want to manipulate an option in our generated code,
we need to know what it looks like in memory.
The thing is that rust likes to be really fast,
it likes to be vague about things so that it can optimize things.
Like the layout of a type, right?
This is a little bit from the rust reference,
and it says, if you have the rust representation of a type,
then there are only three things we guarantee,
and the rest of it, we can just do whatever you want.
So, if you have a first sample struct, then the fields are properly aligned,
they do not overlap, and the alignment is the minimum of the element of the fields.
But they can add like a gigabyte of extra padding if they like.
They can reorder all the fields that they want.
There's no way of telling, and you can't rely on anything you see happening.
Because it might also change between compiler versions,
or maybe even compilations, you should ask some of the rust compiler deaths about that.
So, an option of T, like it could be anything in memory.
So, what we have to do instead is we have to kind of make a run option type,
that is actually very well defined,
that we know what it looks like, so that we can interact with it from roto.
So, we have this thing, a roto option,
and I would like this thing from you.
You don't need to see this thing when you're using roto.
So, under the hood, we take an option, and we transform it into this thing,
which has a wrapper C on it, so that has a very well defined layout.
And then we can actually use it from roto, and then if you pass it back,
if you return it from the script, we should do the inverse conversion
back into a normal rust option.
So, that's that.
That's one more entry in our list.
Okay, now we have a little scripted language,
but we want to do more with this language, right?
There are some types that roto just supports.
It has strings, it has Boolean's, and it is an integer,
and floating point numbers, and even IP addresses,
and some other strange things, just because we needed them.
But this doesn't have everything.
It doesn't have everything that your application might need.
So, how do we fix that?
So, let's assume that your application needs to do
some daytime handling, for example, right?
So, you would like roto to know about daytime's.
Okay, it doesn't.
Like this code does not work out of the box.
But we do have a mechanism that you can register your own types.
It looks like this.
You create a library of things that you want to add to roto,
in a sort of DSL that looks kind of like Rust,
in this library macro, and then you build a runtime with that library.
Now, the Val thing there, you can basically just interpret it
as like this is a custom type of something,
and the clone means roto is allowed to clone this,
but not to do a map copy on it.
Like it can't assume that this thing implements copy.
And the type that we're registering here is just from the GIF grade,
which is a grade for, you know, daytime's and durations,
and all sorts of other complicated time stuff,
and it's really good.
And we just expose that thing to roto.
As kind of like an opaque type.
So, now we can do this.
We can take that daytime thing as an argument,
and we can return it.
So, that's great.
We can also extract that function from,
that we defined in roto.
We can extract that back into Rust.
Okay, so how does that work?
We don't know, I've said before, like, for an option,
we needed to know exactly what it looks like.
How do we handle this kind of type?
That we have no clue about whatsoever.
Well, the first thing is we can't inspect it.
We can't, like, look into it.
We can't read its fields.
We can't, like, if it's an enum,
we can't see the discriminant of the different variants and stuff.
So, it's kind of opaque to us.
That means that we can kind of simplify.
And we could just say, any time we see something like this,
we have a pointer.
And then we just kind of, we, we get the clone function
and the drop function of that value.
And that allows us to kind of move it around and clone it
and do all the things that we need to do with it,
just to be able to sort of pass this through different functions.
It doesn't mean that we can do anything useful with it yet, though.
So, to do anything useful,
we need to add some methods to this type.
Like this one.
It's just this very simple method.
This is not meant to be a full daytime library.
But, you know, maybe you just want to add some days to a daytime.
This is the, the code itself there is how you do that in, in GIF.
But, you can see that still in this library macro.
So, that means that Roto will be able to see this method that you're creating.
And that whole thing works very similar to what I just described.
So, this Roto function is how you extract the function.
It's how you register a function.
We use very similar transformations.
We just go the other way, right?
Where previously we had an option that we transformed it to a Roto option.
Now, we go the other way, we go from a Roto option to an option.
And we just flip the whole thing.
And, and that allows us to do some, do some really cool stuff.
You can add your own types. You can add your own constants.
You can add your own methods and functions and all sorts of stuff.
And that really allows you to tightly integrate this whole thing with your own application.
Now, this is where it gets tricky.
Because I would like Roto to have lists, right?
Lists are great.
What kind of language are you if you don't have any lists?
That would be kind of silly.
So, if we just follow whatever we were doing before,
we just say, well, a list is a vet.
And we just kind of register a vet or something, right?
That should work.
But the problem is that we run this entire thing after Rost has compiled, right?
We don't know anything about the generics of Rost.
That doesn't work.
Because the generics have disappeared.
We can't tell Rost to keep around the code for any possible vet
that it could ever possibly find for any type.
There are generic types do not have type IDs.
They do not have a layout.
If we can't get function pointers to their methods,
we basically can't do anything with them.
Because whenever we're inspecting these things,
the generic type doesn't exist anymore.
So, is that the end of the road then, right?
Are we stuck here?
Yeah.
I want them, right?
Can we not just fake it?
Can we hack around this?
So, this is what I mean with fake generics.
If you have a list of T, right?
You have a list of some element type.
That is roughly equivalent to having a list.
It's just a big buffer of bytes.
And then, we have a V table of some methods that we can use to interact
with the individual elements in that buffer.
So, we know what the sizes of the type,
we know what the alignment is of the type,
and then we know what, for example, the clone that a drop is,
so that whenever we drop our list,
we know how to drop each of the elements in the list.
So, we could just kind of pretend that we know what the type is.
Now, of course, that is unsafe,
because there's nothing stopping us in Rust now
to create a list of U-8s and start adding U-16s to it.
Right?
Because the type parameters gone, there's nothing stopping us.
But, at least, it allows us to do this in Roto.
Right?
It allows us to say something that looks like a list in Roto
is actually this kind of thing.
And we just tell Roto to pretend that there is a type parameter.
And for every method that we register for it,
we also say, now pretend there's an additional type parameter
when you do your type checking on this.
So, the Roto type checker actually enforces
that the individual accesses that you are doing are actually safe.
And that kind of leads to this,
because we also then make a wrapper for that in Rust,
which is safe.
So, we go from a type wrapper in Rust to a race list,
which has no type parameters anymore.
And then we tell Roto to pretend that again has a type parameter.
And that's how we work around the whole thing about the generics.
And instead, we just say, that works.
And that is kind of the most complicated that we've gone so far
in terms of this whole F5 boundary.
Now, let's type some loose threads.
While implementing this, I have learned to not be too clever.
This should come as no surprise.
But I was dealing with the cloning and dropping things by pointer
and doing all sorts of crazy stuff.
And I didn't get that wrong a few times.
But I learned to try the easy thing first,
and only then optimize later.
And there's still many things to optimize,
but at least it works now.
And then we have loads of tests,
and we run Valgrind on these tests.
Now, an interesting thing to note is that we can't run
Mary on everything, because Mary can't run our generated code.
Because we are generating directly machine code,
and Mary interprets an intermediate representation of the press
compiler, so it doesn't actually run machine code that well.
But we can run our entire test suite on a Valgrind,
and it will catch any leaks or double freeze or anything
that my coach and my do wrong, it will hopefully still catch.
Not anything, but many things.
And based on this work that we've done for list,
we want to do some more features,
like we want to add maps or dictionaries,
or however you want to call them to this language.
And then we'd love to at some point be able to access
fields or match on Rust Enems.
But of course, I said before,
like we kind of have to assume that any type you register
is opaque.
We can't look into it, so that will take some more
problem-solving to make that work.
And then we have other wild features planned,
like first-class functions that you can pass in a function
from Roto to Rust and then from Roto,
and then from the Roto calls the Roto function,
and it gets crazy.
All right, that was all I had to say about Roto.
But I'd like to quickly interject
about this thing called Rust Week.
It will be an awesome conference.
There are many of the organizers are also here.
There's Mara and Yana.
There they are.
And it's this May.
It's going to be huge.
It's going to be two days of three tracks.
And you should definitely get tickets.
Now to encourage you to get tickets,
and to thank you for listening to my talk.
There's a little discount code.
For them to be six.
We'll give you 10% off of any tickets that you think
that you want.
We also have some cards lying around.
You can also get with a full schedule and everything,
and you can go to Rust Week.
We've got work to get all the information.
That's all I have.
Thank you.
Any questions?
Thank you for the awesome talk.
I may have misunderstood,
but it looks that you're implementing this list,
essentially with dynamic dispatch.
That you then have like kind of a list of
retailers and each one just represents the function
you can call on that.
I'm just wondering, wouldn't it be more natural
than to also on the Rust site represented as this?
On the Rust site, I don't see like a list which is generic,
but instead like a list which actually contains straight objects,
and then this is also what you have on the road to site.
Yes.
So the question is, could we instead just implement this list
as a list of trade objects, right?
The problem is that that almost works in many cases,
but you can also define your own road to types.
And then I would have to generate trade objects
for those road to types.
Whereas if I have my own VTable implementation,
I know exactly what's in it, and what it looks like,
and I can generate that for any type that I have in Roto.
And so you can even declare a type in Roto
then add that to a list,
even though the list is implemented in Rust.
And that makes it a little bit more complicated.
Okay?
Thank you for the very interesting thoughts.
So what about the performance that you measured
by integrating Roto into Roto?
What were the findings,
and what do you plan to optimize for the project and in general?
So you want to know what the performance measurements are.
What did you get out of it?
Right.
So the performance is pretty good right now.
If you stick to, especially if you stick to numbers.
Right?
So in the German population, a arithmetic, that kind of stuff.
Yeah.
Then it's really good,
and it beats Lua easily.
But what many of these scripting languages do,
that we don't,
is they are very clever about their data structures.
So if you have lists, for instance,
like JavaScript run times are crazy about this,
they will pull all sorts of tricks under the hood
to make those things faster.
And we are liking behind in that.
So if you do lots of string manipulation,
you'll be a little bit slower.
But if you stick to simple stuff,
it will be a lot faster right now.
And that's just something that we'll have to chip away at over time.
Yeah, like copy on rights or like if you.
Yeah.
I have a syntax question.
Is there any particular reason why you chose the square brackets
for the genetics?
Why I chose the square brackets for the genetics?
Because I wanted to avoid the turbofish.
Right?
So the angled brackets in the parts that I kind of conflict
with comparison operations.
And that's why you need colon colon angle brackets
for in rust usually.
And then I figured if I use the square brackets,
then I kind of avoid that.
Although it does kind of conflict with list indexing now,
but that's it there.
You mentioned JavaScript run times.
Do you do anything clever in terms of the just-in-time compilation?
I say you have something in the loop.
Will you only compile it once and then use the machine code
in all the other loop iterations?
Or would you do the jitter compilation every time?
So we don't do super clever stuff because we already have the type
information.
We are already pretty good out of the box.
We could indeed in the future do more like constant propagation
or like other optimizations.
But that is we currently don't.
No.
It's nice to thank you.
Thank you.
There's a really nice talk.
Thank you.