WEBVTT

00:00.000 --> 00:18.000
Hello, everyone. My name is Michael. I'm a system software engineer at Apple. Hello.

00:18.000 --> 00:24.680
My name is Evan. I'm a compiler engineer on the 15-met Apple. So, the process of porting a programming

00:24.680 --> 00:29.680
language to a new system involves intimate understanding of the internal workings of the language

00:29.680 --> 00:34.680
and the system you're porting it to. It also requires a healthy dose of patience for

00:34.680 --> 00:40.680
when things inevitably go horribly wrong. You often have to deal with situations where

00:40.680 --> 00:44.680
print statements aren't printing and the debugger isn't debugging, so you have to cobble together

00:44.680 --> 00:50.680
whatever tools are available on the OS to figure out what's going wrong and fix it. So, this is the story

00:50.680 --> 00:55.680
of how we dance with the demons and ported Swift to FreeBSD.

00:55.680 --> 01:01.680
We'll start with a quick bit of history on previous porting efforts.

01:01.680 --> 01:07.680
And then we'll break down the bootstrap that we used to bring up Swift. It's four steps.

01:07.680 --> 01:12.680
Then we'll waltz our way through Build Errors, getting Swift built on FreeBSD. And finally, we'll

01:13.680 --> 01:17.680
quick step through the code. Squashing bugs as we go.

01:17.680 --> 01:22.680
So, this isn't the first time that Swift has been ported to FreeBSD.

01:22.680 --> 01:28.680
The first porting effort landed in the Swift repository in December 15 or December

01:28.680 --> 01:34.680
of 2015 shortly after Swift was opened sourced as part of Swift 2.2.

01:35.680 --> 01:45.680
Roughly six months later, in May of 2016, the first ports in FreeBSD were landed.

01:45.680 --> 01:49.680
And then things went quiet for about six years.

01:49.680 --> 01:54.680
And without CI support, things kind of bit rotted.

01:54.680 --> 02:03.680
So, then between 2022 and 2023, 18 commits were published to the Swift repository to revive the build.

02:03.680 --> 02:10.680
Now, these efforts were driven by community members. They didn't have the backing CI support.

02:10.680 --> 02:18.680
And they also didn't get the test suite fully running cleanly.

02:18.680 --> 02:29.680
Now, in 2024, Swift, unfortunately, was no longer bootstrapable from an image that didn't have an existing Swift

02:29.680 --> 02:33.680
installation. This broke some time in Swift 6.0.

02:33.680 --> 02:41.680
So, in May of 2025, I brought the bootstrap back in Swift 6.2.

02:41.680 --> 02:49.680
Then, in June of 2025, an unofficial Swift 510 was uploaded to the FreeBSD port street.

02:49.680 --> 02:52.680
Which brings us to today?

02:52.680 --> 02:56.680
So, given the efforts of the past, what are we doing differently to ensure that it doesn't

02:56.680 --> 03:04.680
come out again? Well, we're working toward a package that tests cleanly across all of the tools in the tool chain and across the ecosystem.

03:04.680 --> 03:11.680
And we also have CI support set up, so that we can ensure that things keep working going forward.

03:11.680 --> 03:25.680
Let's talk about Swift. Swift is a general purpose programming language that makes a very nice balance between safety and performance without getting rid of rigability.

03:25.680 --> 03:40.680
The package from Swift.org contains a tool chain, some developer tools, a Swift standard library, various platform libraries, and testing libraries.

03:40.680 --> 03:50.680
Now, the tool chain is used to compile the libraries, but it also depends on the libraries, so we have a cycle that we need to break.

03:50.680 --> 04:03.680
Now, we can't build a Swift standard library without a Swift compiler, so I had to aggressively start chopping pieces out of the compiler until we were left with something that didn't have any Swift left.

04:03.680 --> 04:10.680
I did this initial work in Ubuntu 2404 because we already have support for Ubuntu 2404.

04:10.680 --> 04:19.680
So, any test failures that we see out of the bootstrap are because the bootstrap failed to converge and not because we have missing platform support.

04:19.680 --> 04:29.680
So, we take that partial compiler and use it to build the standard library and lib dispatch, lib dispatch is needed for concurrency.

04:29.680 --> 04:34.680
This makes up our stage zero artifact.

04:34.680 --> 04:42.680
We use that stage zero artifact to build a new Swift compiler that uses the stage zero runtimes.

04:43.680 --> 04:50.680
The stage zero compiler is pretty brittle and it can't actually build a whole lot more than just what's here without crashing.

04:50.680 --> 04:59.680
And anything that it does build is kind of an questionable state because the compiler is quite literally missing major portions of how it works.

04:59.680 --> 05:04.680
So, it can very easily miscompile what it does build.

05:05.680 --> 05:14.680
So, we now take our more complete compiler and build the standard library dispatch and foundation again.

05:14.680 --> 05:21.680
This collection becomes our stage one artifact, which we then use to build a stage two.

05:21.680 --> 05:30.680
Now, technically this compiler, the stage one compiler is able to build the entire tool chain without crashing.

05:30.680 --> 05:36.680
But that tool chain doesn't pass the test suite, so we haven't actually converged yet.

05:36.680 --> 05:43.680
And so, to save time, we only build a subset of the tools.

05:43.680 --> 05:48.680
Now, we can take this stage two tool chain and build the complete tool chain.

05:48.680 --> 05:54.680
That full tool chain passes the test suite, so our bootstrap has converged and we can stop.

05:54.680 --> 06:00.680
Now that we have a path to bringing up a tool chain, it's time to start building on 3bst.

06:00.680 --> 06:01.680
Over you Michael.

06:01.680 --> 06:04.680
Thank you.

06:04.680 --> 06:08.680
Once we get the tuition boost draft is mostly mechanical.

06:08.680 --> 06:13.680
Most of the work is we need to address the compiler errors and warning as they come up.

06:13.680 --> 06:22.680
And then we address some testing failures and we rebuild the tool chain again and we repeat it again and again.

06:22.680 --> 06:34.680
But fortunately, because of the previous effort, we have some speculative support in Swift already, so it makes things a lot smoother.

06:34.680 --> 06:41.680
That says, we do run into some really quirky, interesting issues along the way that you may not expect.

06:42.680 --> 06:47.680
The first one is ellipsy pass pass, so LLVM has this concept called module map.

06:47.680 --> 06:51.680
And not to be confused with the standard C pass pass modules.

06:51.680 --> 06:58.680
Since 3bst uses LLVM and import it, it also inherits the module maps that comes with it.

06:58.680 --> 07:08.680
Except the module maps in 3bst including 3bst 14 is a bit outdated, and it does not export the modules we need to come down Swift.

07:08.680 --> 07:18.680
As we saw in the early stage when we were talking in 14.0, we actually need to go into the base system and manually patch those module maps, such that we can just build Swift.

07:18.680 --> 07:28.680
Fortunately, this is all addressed in 14.3 when Swift, when 3bst import a new version of LLVM, and this is no longer required.

07:28.680 --> 07:37.680
Another very interesting issue is, stand the pair API, API, you'd be like, what that just a stand that leaves C pass pass feature?

07:37.680 --> 07:48.680
Well, except turns out 3bst has stuck with older C pass pass, API for a very long time, when the standard pair does not have a triple copy constructor.

07:48.680 --> 07:57.680
Now, this is a rather easy face, which is like we move all the bits in the Swift compiler that uses the triple copy constructor, and we place with something more explicit.

07:57.680 --> 08:01.680
Now, with that figure, let's look into debugging.

08:01.680 --> 08:09.680
Porting a new tool, porting an entire tool to a new system is hard. There can be incorrect assumptions.

08:09.680 --> 08:17.680
There could be like black king debugging tools at all, and generally speaking, many things can go wrong.

08:17.680 --> 08:23.680
However, while also on 3bst and 3bst by default include a lot of debug utilities.

08:23.680 --> 08:37.680
So, in the next couple of slides, we're going through a couple of very common issues, like crashes, hands, and just other issues, and see how we can fix them.

08:37.680 --> 08:45.680
Let's use a Swift PM, which is an example. This is a step that we build the two shrink with Swift package manager.

08:45.680 --> 08:51.680
We know something is wrong here, obviously, but it tells us a little about why.

08:51.680 --> 08:57.680
And also, notice how the Python script is just like swallowed all the standard error and standard out and backfaces.

08:57.680 --> 09:01.680
So, what should we do?

09:01.680 --> 09:07.680
Well, first, let's tackle what really goes wrong. The build process was Swift project.

09:07.680 --> 09:15.680
It involves passing artifacts and output between multiple components, like Swift driver, Swift package, Swift one, and so on.

09:15.680 --> 09:21.680
So, first, I will be figuring out how these commands are invoked, and then are they invoked correctly?

09:22.680 --> 09:28.680
3bst includes D trays in the base system, so we can easily use that to achieve this goal.

09:28.680 --> 09:39.680
And to make it even better, 3bst also includes DWatch, which is less known, but it's a really nice replica for D trays, and by default includes color output, just make things whole lot easier.

09:39.680 --> 09:57.680
So, here we see what you think, DWatch, to trace every single process invocations when we're building Swift, and we can verify that if we have passing wrong command line, argument, and if a process has been exit way too early, that is supposed to be.

09:57.680 --> 10:07.680
So, one of our first goals is really, we covered the standard out and standard error, as it gives us information about what could really go wrong, and we can eyeball it.

10:07.680 --> 10:20.680
So, to do that, we write a really simple D trays one line there, essentially, which is just tapping into the right system code, and just dump the standard out and standard error out.

10:20.680 --> 10:33.680
In this particular example, we actually see the compiles actually crashing, and it means a code dump, and we can now see the code dump and see the context by the crashing and go on and fix them.

10:33.680 --> 11:02.680
But, what if is the opposite? What if the process just never want to exit and just hands there? We can also do DWatch this with D trays. So, it's another one line there here. What we're doing here is, essentially, is to profile the user space stack, every 100 milliseconds, and this would give us a sample of different stack trays, and we can compare, and then try to figure out, is it really hanging, is it making thorough progress, or is it something else?

11:02.680 --> 11:13.680
So, we can talk all the about D trays and how we use D trays to solve bugs, but because time, let's look into something that's a bit more interesting.

11:13.680 --> 11:25.680
So, one, everyone's a wow, we can see this a lot, it's like a polluting our standard out, and we're like, okay, what is this? And it's definitely not coming from Swift, so it's definitely a previous D thing.

11:26.680 --> 11:53.680
So, turns out, for VSC, it's a nice feature that will warn you if a threat has exit with left over a threat specific data. So, what does that really mean? It just means, like, you're setting up some threat local storage, you attach it, destructed to it, that destructed get runs multiple times, but the TLS is still not now. So, something must go wrong. So, this is Swift telling us that something that some bugs going on here.

11:53.680 --> 12:05.680
So, we can just go on to all the dependency and the Swift codebase and try to figure out wherever we use threat local storage if there's something wrong, but we're on 3BSD, so we can do something better.

12:05.680 --> 12:16.680
So, instead of going through our codebase, we just go to BSD, and then we just patch the base system, and just we build everything. This gives us a couple of bonus.

12:16.680 --> 12:25.680
First of all, turns out, we're building the whole OS can actually faster than we're building the compiler, and then it's high to a tree.

12:25.680 --> 12:39.680
Second, we can make it crash, so we can get a call done, and lastly, if we really worry about messing up the whole OS, because we're building it, we can just use a boot environment and to restore it later.

12:39.680 --> 12:54.680
After applying the log, this is what we get instead, and now we can see way more data, and say, okay, hold on a second, that pointer get invoked multiple times, exactly four times, and something to do up. So, what is in that address?

12:54.680 --> 13:03.680
Because we actually make it crash, we can now take this call done and take this address into LDB, actually from the base system, because we don't need a Swift bit to support this.

13:03.680 --> 13:19.680
And we can tell now it's actually coming from a call foundation, okay, so let's fix that. Without taking too much details, we apply the fix instead of setting that pointer, we're setting to now, and now we fix it, and everything's well.

13:19.680 --> 13:34.680
So, turns out the trace is very useful, and it's grateful to debugging, and also turns out patching and rebuilding the whole OS is a rather efficient debugging techniques if you can do it efficiently with that bombshell off to you.

13:34.680 --> 13:45.680
Thank you, Michael. Let's talk about dispatch. So, lib dispatch is a library for managing asynchronous work. It's also the default backend for Swift concurrency.

13:45.680 --> 13:53.680
It uses core accounts to help it determine how many threads to initialize thread pools with, and to help detect and avoid dead block states.

13:53.680 --> 14:00.680
While bringing up dispatch on free bst, we had this little test value come up. Doesn't tell us a lot, so let's look at the test.

14:00.680 --> 14:12.680
It's verifying that we will initialize a new thread if all of the existing threads have been consumed with existing work, and some new work items are added.

14:12.680 --> 14:17.680
So, it saturates all of the cores with a timeout call and a bunch of spin calls.

14:17.680 --> 14:25.680
Then, it schedules a call to this race flag function, which just increments a flag in the context object.

14:25.680 --> 14:31.680
The initial timeout function is the one where the interesting stuff happens.

14:31.680 --> 14:38.680
So, it sleeps for two seconds, which gives dispatch a chance to initialize the new threads and launch and run race call.

14:38.680 --> 14:45.680
Once it returns from that sleep, it checks that the flag has been implemented to one. Yes, there's a race condition here. We know.

14:45.680 --> 14:52.680
But, it just verifies that that is one and then goes on its merry way.

14:52.680 --> 14:59.680
Now, the bug turned out to be in how the test found the number of cores to initialize everything with.

14:59.680 --> 15:04.680
FreeBSD doesn't have the hardware active CPUs, this call.

15:04.680 --> 15:14.680
This cuddle by name here was failing and returning a failure code that wasn't being checked, and so active CPU wasn't being set to a reasonable value.

15:14.680 --> 15:19.680
So, this is a good reminder to check your return codes even in your test cases.

15:19.680 --> 15:23.680
Now, the fix for this test is fairly simple.

15:23.680 --> 15:30.680
Use this cuff to get the number of online processors, not terribly exciting, so why am I showing it to you?

15:30.680 --> 15:40.680
Well, it's a little bit more involved than that, just querying for the number of online cores gives you all of the cores across the entire system.

15:40.680 --> 15:52.680
There are things like C groups on Linux and Jails on FreeBSD, where you can limit the number of cores that are available to an application.

15:52.680 --> 15:58.680
The discussion that came from fixing this test actually resulted in changes being merged into FreeBSD.

15:58.680 --> 16:06.680
With the most recent release, according for the number of online cores with CISComp will give you the number of cores that are available to your application,

16:06.680 --> 16:12.680
respecting limitations set by Jails, not all of the cores across the whole system.

16:12.680 --> 16:16.680
So, this is Swift already influencing how FreeBSD works.

16:16.680 --> 16:18.680
Let's talk about concurrency.

16:18.680 --> 16:21.680
So, we had a test that looked like this.

16:21.680 --> 16:24.680
It's supposed to sleep for five seconds, and then return.

16:24.680 --> 16:31.680
But it was hanging somewhere in that sleep, and being timed out after about an hour.

16:31.680 --> 16:34.680
So, using ProxStat, we can see what the threads are up to.

16:34.680 --> 16:36.680
There's one that's on 6th to spend.

16:36.680 --> 16:40.680
There's another that's sitting on a K event call.

16:41.680 --> 16:50.680
I happen to know from knowing dispatch that the timer mechanism is using K event and timer events under the hood.

16:50.680 --> 16:58.680
So, I also know that this is the first time that we are using the KQ back end for dispatch on a non-apple platform.

16:58.680 --> 17:02.680
So, there are probably some constants that are not correctly set in there.

17:02.680 --> 17:04.680
Seems suspicious.

17:04.680 --> 17:09.680
Popping to open in the debugger, we are definitely on a K event call.

17:09.680 --> 17:15.680
And then LLGP crashed on me, so I couldn't get any of the information about what the event was.

17:15.680 --> 17:23.680
And, relaunching LLDV, it refused to re-disembolicate this file, so I couldn't set breakpoints here anymore.

17:23.680 --> 17:28.680
So, what do we do? Back to detrace.

17:28.680 --> 17:30.680
So, I wrote this little script here.

17:30.680 --> 17:37.680
It just tracks everywhere in our test program, where we are calling K event on a timer event.

17:37.680 --> 17:43.680
And then it prints out what the flags were and information about the event.

17:43.680 --> 17:48.680
When we run the script, we see three events get registered.

17:48.680 --> 17:54.680
Cool. That last one there has a raw time that looks really similar to the time we're looking for.

17:54.680 --> 18:05.680
But, that filter flag is set to zero, which on 3DSD means milliseconds.

18:05.680 --> 18:08.680
So, our value is like six orders of magnitude to be here.

18:08.680 --> 18:15.680
We didn't ask it to wait for five seconds. We asked it for 57 days.

18:15.680 --> 18:25.680
Okay, so we could do some division or just set the nanosecond flag and say that it is in nanoseconds, not milliseconds.

18:25.680 --> 18:30.680
Catch that and we're golden ways for five seconds returns.

18:30.680 --> 18:37.680
We're happy that those other events seem to be part of the event loop, which is also getting service at a more reasonable cadence now.

18:37.680 --> 18:41.680
So, this is detrace rescuing us once again.

18:41.680 --> 18:44.680
Now, let's talk about metadata.

18:45.680 --> 18:52.680
So, it has runtime metadata is used by runtime and debugger for things like reflection and runtime type information.

18:52.680 --> 19:01.680
The LDB test suite was crashing in some places and also just saying that there was no metadata in others.

19:01.680 --> 19:03.680
So, it was a bit tricky.

19:03.680 --> 19:08.680
There's a tool called Swift Reflection Dump, which extracts this data for us.

19:08.680 --> 19:10.680
It runs on a Mac and it can open things.

19:10.680 --> 19:15.680
So, I just took the files from 3DSD, sung along to my Mac, and bam, it crashed.

19:15.680 --> 19:17.680
Cool. That's great.

19:17.680 --> 19:19.680
Crashes are wonderful.

19:19.680 --> 19:24.680
Attaching a debugger, point this right out where we're looking for.

19:24.680 --> 19:29.680
It's a call to string length, which is trying to get the section name length.

19:29.680 --> 19:32.680
There are a few variables that contributed to this call.

19:32.680 --> 19:34.680
So, let's start poking around.

19:34.680 --> 19:39.680
So, the string table, that's the kind of the main thing we're looking at here, looks fine.

19:39.680 --> 19:41.680
That looks like a string table.

19:41.680 --> 19:43.680
P-C, let's keep going.

19:43.680 --> 19:47.680
Start pointer, looks like a pointer, but it's kind of too big.

19:47.680 --> 19:48.680
Something's up with that.

19:48.680 --> 19:51.680
And if we ask LDB to jump the memory, it says no.

19:51.680 --> 19:52.680
Can't do it.

19:52.680 --> 19:55.680
Okay, that looks a little sketchy.

19:55.680 --> 19:57.680
Let's print out the other variables.

19:57.680 --> 20:00.680
The table size is 652 bytes.

20:00.680 --> 20:03.680
Okay, seems reasonable.

20:03.680 --> 20:05.680
Offset, that's way too big.

20:05.680 --> 20:07.680
Way too big.

20:07.680 --> 20:12.680
And if we look at it in hex, that's definitely poison of some sort.

20:12.680 --> 20:20.680
All right, but the issue with this number is it's coming directly from the section header name offset field.

20:20.680 --> 20:23.680
So, what's up with that?

20:23.680 --> 20:24.680
Uh-oh.

20:24.680 --> 20:26.680
Our header looks like garbage.

20:27.680 --> 20:31.680
So, did we completely break the linker?

20:31.680 --> 20:33.680
Did we completely break the compiler?

20:33.680 --> 20:35.680
What is going on here?

20:35.680 --> 20:40.680
So, it turned out that this was the header for the fourth section.

20:40.680 --> 20:44.680
So, let's start looking at it and read out.

20:44.680 --> 20:46.680
Looks okay to me.

20:46.680 --> 20:47.680
Looks good.

20:47.680 --> 20:49.680
Says the new version.

20:49.680 --> 20:50.680
Looks fine.

20:50.680 --> 20:51.680
Okay.

20:51.680 --> 20:55.680
Maybe read out if it's falling back on some clever error handling.

20:55.680 --> 21:02.680
So, we've got the header section header offset of 2b40 and 64 bytes.

21:02.680 --> 21:06.680
Uh, the fourth section will be at 2c40.

21:06.680 --> 21:09.680
Popping it open in our favorite hex editor.

21:09.680 --> 21:11.680
Looks good.

21:11.680 --> 21:12.680
Cool.

21:12.680 --> 21:13.680
It's little andian.

21:13.680 --> 21:17.680
It's x8664, so little andian, but looks good.

21:17.680 --> 21:21.680
So, I don't think read out if it's falling back on anything.

21:21.680 --> 21:27.680
Um, this suggests that there's something all going on between where we are parsing the data and reading the data out of the file.

21:27.680 --> 21:29.680
Because the file was fine.

21:29.680 --> 21:32.680
Um, speeding things up here.

21:32.680 --> 21:38.680
File buffer wasn't set here in this lambda, but so we were falling back on this read bytes thing.

21:38.680 --> 21:39.680
Read bytes?

21:39.680 --> 21:41.680
Not too much.

21:41.680 --> 21:42.680
Let's keep going.

21:42.680 --> 21:44.680
There's get content set address.

21:44.680 --> 21:46.680
That looks a little bit more interesting.

21:46.680 --> 21:50.680
If image coming out of this decode image index and address isn't set,

21:50.680 --> 21:53.680
we'll return an empty string graph.

21:53.680 --> 21:54.680
Hmm.

21:54.680 --> 21:56.680
Well, here's what's going on.

21:56.680 --> 21:58.680
We're iterating through the images.

21:58.680 --> 22:01.680
And if none are found, we'll return an old pointer.

22:01.680 --> 22:02.680
Yes, what we were returning.

22:02.680 --> 22:03.680
No pointer.

22:03.680 --> 22:06.680
So, we are definitely returning an empty string graph.

22:06.680 --> 22:08.680
So, where did these images come from?

22:08.680 --> 22:10.680
And why don't they have our data?

22:10.680 --> 22:17.680
Well, uh, they're being constructed here, but that doesn't tell us a whole lot.

22:18.680 --> 22:22.680
So, following that image constructor, uh, there's this scan elf type thing,

22:22.680 --> 22:26.680
which is going through and segmenting our binary and applying relocations,

22:26.680 --> 22:28.680
kind of like a mini dynamic loader.

22:28.680 --> 22:32.680
If we take a step back, the metadata and swift is structured data.

22:32.680 --> 22:35.680
It contains references within itself.

22:35.680 --> 22:41.680
And some of these reference required that the data has been relocated to be meaningful.

22:41.680 --> 22:43.680
So that makes sense.

22:43.680 --> 22:48.680
For those of you who know what's no elf, that might be triggering some warnings.

22:48.680 --> 22:49.680
Let's talk about that.

22:49.680 --> 22:52.680
So, this is an elf file, roughly.

22:52.680 --> 22:56.680
Um, the dynamic loader goes and reads the program headers.

22:56.680 --> 23:00.680
And that tells it how to chunk up the file and where to put each chunk.

23:00.680 --> 23:01.680
These are called the segments.

23:01.680 --> 23:05.680
Elf tries to balance, uh, load times with memory consumption.

23:05.680 --> 23:10.680
So, these, if you make the segments too big, uh, there will be fewer of them.

23:10.680 --> 23:15.680
So, it loads faster, but you'll be pulling in more data than you actually need.

23:15.680 --> 23:20.680
Uh, now this might pull in some of the control structures, like parts of the section header table.

23:20.680 --> 23:25.680
So, in elf section header tables are not guaranteed to be loaded in any segment.

23:25.680 --> 23:28.680
But that's what we were trying to read.

23:28.680 --> 23:30.680
Now, why is the runtime okay?

23:30.680 --> 23:34.680
Well, the runtime wouldn't see the bug, because these sections are allocated.

23:34.680 --> 23:39.680
They have start stop symbols, which surround each of the sections.

23:39.680 --> 23:40.680
Uh, sections.

23:40.680 --> 23:43.680
The runtime uses that to load the data.

23:43.680 --> 23:44.680
That's fine.

23:44.680 --> 23:45.680
Symbols totally work.

23:45.680 --> 23:46.680
The data still exists.

23:46.680 --> 23:49.680
It's just the section headers that are bad.

23:49.680 --> 23:54.680
Now, we haven't resolved this bug yet, because we're still weighing some options.

23:54.680 --> 23:59.680
But that was a pretty nasty bug to deal with.

23:59.680 --> 24:01.680
So, we dance with the demons.

24:01.680 --> 24:02.680
We resurrected a bootstrap.

24:02.680 --> 24:07.680
We waltz through the build errors and, um, quick step with tools like detrace.

24:07.680 --> 24:10.680
And got Swift working more or less on FreeBSD.

24:10.680 --> 24:12.680
Thank you for listening to our talk.

24:12.680 --> 24:14.680
If you are interested,

24:14.680 --> 24:24.680
if you want to get involved in this, uh, the forums.swift.org,

24:24.680 --> 24:25.680
because it's a good place to reach us.

24:25.680 --> 24:31.680
And if you want to try this package, we have a nightly preview that you can try out.

24:31.680 --> 24:33.680
Um, yeah.

24:34.680 --> 24:37.680
And if you're on FreeBSD 15, you'll need to install the compact package.

24:37.680 --> 24:40.680
We've been doing development on FreeBSD 14.

24:40.680 --> 24:42.680
But, uh, thank you.

24:42.680 --> 24:43.680
Thank you.

24:43.680 --> 24:50.680
Thank you.

