A reminder that I’ll be speaking at Scale 17x in Pasadena on Sunday, March 10th about all the cool stuff we’ve been doing in Builder and how that plays into the role of modernizing our development stack.
As I mentioned in my overview of the upcoming Builder release, a lot of this development cycle focused on improving machinery the user does not see. In the past four years of development, a number of patterns emerged. When I started this project, I had some ideas of how to keep things organized, but Builder quickly grew beyond my ability to keep the whole design in my head at once.
Thankfully, I’ve worked on many large software teams during my tenure at companies like VMware and MongoDB. These company’s products have something in common, in that they’re a number of sub-systems performing specialized tasks that coordinate with each other. Not surprising, I know. But sometimes, knowing where to draw the lines between sub-systems is what differentiates products. (Interestingly, if you want to build a database engine, you better have a good reason to deviate from page 144 of Architecture of a Database System).
Now days, Builder segments the code into a series of static libraries. We have a core library that provides the basic functionality used by all sub-systems. We have a process and threading library. One library deals with representing and manipulating source code. Yet another focuses on building, executing, testing, and packaging software. To enable Language Servers, we have one too. Even the gui and editor are somewhat compartmentalized.
In the end, we link all of these static libraries into the final executable, which is then processed to generate GObject Introspection data; the
*.typelib. That means that our plugins, written in C, C++, Python, or Vala, do not need to rely on linking against any shared libraries. All symbols come from the executable with
-export-dynamic. (One side-effect of this, is that we no longer allow linking our Vala based plugin into the executable, since it relies on
To keep startup fast, all of our bundled C-based plugins are compiled into the final executable. That reduces the number of link loader work, file-system directory scanning and iops, and allows for more aggressive compiler optimizations. We also avoid loading some plugins in the early initialization phase, instead deferring that work until later.
The biggest change in the refactoring is IdeObject. This object has become somewhat like a multi-threaded version of what
GtkObject used to be. It allows us to build a top-down object tree. The root of the tree is a specialized form, IdeContext. Each sub-system mounts itself on this tree. Plugins may have or work with objects that are descendants of the sub-systems. In the end, it makes things much easier for me to debug at runtime because I can get a clearer picture of how things interact and what plugins are active. One interesting feature of
IdeObject is that we guarantee they are only ever finalized in the main thread.
The sub-system that I think resulted in the most cleanup was
libide-foundry.a. This contains the build manager, pipelines, and extension points for plugins to provide the the abstraction glue. It also contains runtime abstractions, execution management, testing infrastructure, device management (phone, tablet, simulator), and toolchains. Currently, the debugger engine is a separate library, but I may fold it into this library for completeness.
When cleaning up
libide-code.a, I simplified the buffer management. This code was a bit temperamental because I put a few too many features into the base class. I think that happened simply because I couldn’t yet foresee many of the ways it would be extended. Seeing how the code evolved over time allowed me to create better abstractions and extract those features.
What kicked off this whole process was an hour hack to add multi-monitor support. That somehow turned into a 2 month project that involved me ripping everything apart and putting it back together because of our initial design. I’m happy to say that I think things turned out quite well and we are poised to keep moving fast for the next few development cycles to come.
We’re getting many more requests to support external plugins in Builder. I’ve always taken the Linux kernel approach here in that I very much want to encourage upstream contributions over external plugins. However, I am starting to loosen up just a bit and we’ve added the
X-Builder-ABI=3.31 key to plugin definitions. This allows us to continue to evolve our APIs but promise to not break them in micro releases. My expectation, however, is that this major refactoring will set us down the path towards some amount of ABI stability.
In my next post I’d like to cover how the UI components are abstracted and give you an idea of which plugin interfaces to use and when. In the mean time, I have plenty of documentation to write.
We just landed the largest refactor to Builder since it’s inception. Somewhere around 100,000 lines of code where touched which is substantial for a single development cycle. I wrote a few tools to help us do that work, because that’s really the only way to do such a large refactor.
Not only does the refactor make things easier for us to maintain but it will make things easier for contributors to write new plugins. In a future blog post I’ll cover some of the new design that makes it possible.
Let’s take a look at some of the changes in Builder for 3.32 as users will see them.
First we have the greeter. It looks similar as before, although with a design refresh. But from a code standpoint, it no longer shares it’s windowing with the project workspace. Taking this approach allowed us to simplify Builder’s code and allows for a new feature you’ll see later.
Builder now gives some feedback about what files were removed when cleaning up old projects.
Builder gained support for more command-line options which can prove useful in simplifying your applications setup procedure. For example, you can run
gnome-builder --clone https://gitlab.gnome.org/GNOME/gnome-builder.git to be taken directly to the clone dialog for a given URL.
The clone activity provides various messaging in case you need to debug some issues during the transfer. I may hide this behind a revealer by default, I haven’t decided yet.
Creating a new project allows specifying an application-id, which is good form for desktop applications.
We also moved the “Continue” button out of the header bar and placed it alongside content since a number of users had difficulty there.
The “omni-bar” (center of header bar) has gained support for moving through notifications when multiple are present. It can also display buttons and operational progress for rich notifications.
Completion hasn’t changed much since last cycle. Still there, still works.
Notifications that support progress can also be viewed from our progress popover similar to Nautilus and Epiphany. Getting that circle-pause-button aligned correctly was far more troublesome than you’d imagine.
The command-bar has been extracted from the bottom of the screen into a more prominent position. I do expect some iteration on design over the next cycle. I’ve also considered merging it into the global search, but I’m still undecided.
Also on display is the new project-less mode. If you open Builder for a specific file via Nautilus or
gnome-builder foo.c you’ll get this mode. It doesn’t have access to the foundry, however. (The foundry contains build management and other project-based features).
The refactoring not only allowed for project-less mode but also basic multi-monitor support. You can now open a new workspace window and place it on another monitor. This can be helpful for headers, documentation, or other references.
The project tree has support for unit tests and build targets in addition to files.
Build Preferences has been rebuilt to allow plugins to extend the view. That means we’ll be able to add features like toggle buttons for
meson_options.txt or toggling various clang/gcc sanitizers from the Meson plugin.
The debugger has gone through a number of improvements for resilience with modern gdb.
When Builder is full-screen, the header bar slides in more reliably now thanks to a fix I merged in gtk-3-24.
As previewed earlier in the cycle, we have rudimentary glade integration.
Also displayed here, you can select a Build Target from the project tree and run it using a registered
Files with diagnostics registered can have that information displayed in the project tree.
The document preferences have been simplified and extracted from the side panel.
The terminal now can highlight
filename:line:column patterns and allow you to ctrl+click to open just like URLs.
In a future post, we’ll cover some of what went into the refactoring. I’d like to discuss how the source tree is organized into a series of static libraries and how internal plugins are used to bridge subsystems to avoid layering violations. We also have a number of simplified interfaces for plugin authors and are beginning to have a story around ABI promises to allow for external plugins.
If you just can’t wait, you can play around with it now (and report bugs).
flatpak install https://gitlab.gnome.org/GNOME/gnome-apps-nightly/raw/master/gnome-builder.flatpakref
Until next time, Happy Hacking!
One of the things we’ve wanted in Builder for a while is a designer. We’ve had various prototypes in the past to see how things would have worked out, and mostly just punted on the idea because it seemed like Glade served users better than we would be able to directly.
Last week, Juan Pablo, Matthias Clasen and I met up in San Francisco to see what we could do in the short term. We discussed a couple of options that we have going forward.
- Integrate glade 3 into Builder using libgladeui.
- Integrate glade 3 using the external Glade application and use D-Bus to inter-operate.
Like all projects, we have some constraints.
- Gtk 4 is in progress, and our hope is that most new application development moves towards that because the benefits are outstanding. That means the value of a Gtk 3 designer is depreciating.
- Gtk 4 changes many fundamental designs behind the scenes. While much effort has been done to reduce the friction in porting applications, porting an UI designer is no trivial task as they necessarily reach into library internals. It is likely Gtk 4 will require creating a new designer from the ground up. Doing this as part of Gtk itself is probably worthwhile.
- We want the designer to know about all of your .ui files so that it is easier to see widgets created using composition.
- Allow generating signal callbacks into your existing code-base in a variety of languages.
With that in mind, I want to get the maximal benefit with the least amount of time to ship. I made a new plugin for Builder last week that gets us moving in that direction. It still needs more work to integrate with signal editing, templates, and other more advanced Glade features.
Hopefully that happens soon because I know we’ve all been waiting for it. Get it now with the Builder Nightly flatpak.
flatpak install --from https://git.gnome.org/browse/gnome-apps-nightly/plain/gnome-builder.flatpakref
One dead give-away of a GNOME/Gtk programmer is how they format their headers. For the better part of two decades, many of us have been trying to keep things aligned. Whether this is cargo-culted or of real benefit depends on the reader. Generally, I find them easier to filter through.
Unfortunately, none of
uncrustify have managed to exactly represent our style which makes automated code formatting tools rather problematic. Especially in an automated fashion.
For example, notice how the types and trailing asterisks, stay aligned, in multiple directions.
FOO_EXPORT void foo_do_something_async (Foo *self, const gchar * const *params, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); FOO_EXPORT Bar *foo_do_something_finish (Foo *self, GAsyncResult *result, GError **error);
Keeping that sort of code aligned is quite a pain. Even for vim users who can fairly easily repeat commands. Worse, it can explode patches into unreadable messiness.
Anyway, I added a new command in Builder last night that will format these in this style so long as you don’t do anything to trip it up. Just select a block of function declarations, and run
format-decls from the command bar.
It doesn’t yet handle vtable entries, but that shouldn’t be too painful. Also, it doesn’t handle miscellaneous other C code in-between declarations (except
Since my initial announcement of Builder at GUADEC in 2014, I’ve had a vision in the back of my mind about how I’d like completion to work in Builder. However, there have been more important issues to solve and I’m just one person. So it was largely put on the back burner because after a few upstream patches, the GtkSourceView design was good enough.
However, as we start to integrate more external tooling into Builder, the demands and design of what those completion layers expect of the application have changed. And some of that is in conflict with the API/ABI we have in the long-term stable versions of GtkSourceView.
So over the past couple of weeks, I’ve built a new completion engine for Builder that takes these new realities into account.
It has a number of properties I wanted for Builder such as:
Reduced Memory and CPU Usage
Some tooling wants to give you a large set of proposals for completion and then expects the IDE to filter in the UI process. Notably, this is how Clang works. That means that a typical Gtk application written in C could easily have 25,000 potential completion proposals.
In the past we mitigated this through a number of performance tricks, but it still required creating thousands of GObjects, linked lists, queues, and such. That is an expensive thing to do on a key-press, especially when communicating with a sub-process used for crash-isolation.
So the new completion provider API takes advantage of GListModel which is an interface that focuses on how to have a collection of GObjects which don’t need to be “inflated” until they’ve been requested. In doing so, we can get our GVariant IPC message from the
gnome-builder-clang sub-process as a single allocation. Then, as results are requested by the completion display, a GObject is inflated on demand to reference an element of that larger GVariant.
In doing so, we provide a rough upper bound on how many objects need to be created at any time to display the results to the user. We can also still sort and filter the result set without having to create a GObject to represent the proposal. That’s a huge win on memory allocator churn.
Consistent and Convenient Refiltering
Now that we have external tooling that expects UI-side refiltering of proposals, we need to make that easier for tooling to do without having to re-query. So the fuzzy search and highlighting tools have been moved into IdeCompletion for easy access by completion providers.
As additional text is provided for completion, the providers are notified to perform filters on their result set. Since the results are GListModel-based, everything updates in the UI out-of-band nicely with a minimal number of gsignal emissions. Compare this to GtkTreeModel which has to emit signals for every row insertion, change, or deletion!
When working with completions for programming languages, we’re often dealing with 3 potential groups of content. The return value, the name and possible parameters, and miscellaneous data. To get the styling we want for all of this, I chose to forgo the use of GtkTreeView and use widgets directly. That means that we can use CSS like we do everywhere else. But also, it means that some additional engineering is required.
We only want to create widgets for the visible rows, because otherwise we’re wasting memory and time crunching CSS for things that won’t be seen. We also want to avoid creating new widgets every time the visible range of proposals is changed.
The result is IdeCompletionListBox which is a GtkBox containing GtkListBoxRow and some GtkSizeGroups to give things a columnar effect. Because the number of created widgets is small things stay fast and snappy while giving us the desired effect. Notably, it implements GtkScrollable so if you place it in a GtkScrolledWindow you still get the expected behavior.
Further more, we can adjust the window sizing and placement to be more natural for code-related proposals.
Dynamic Priority Control
We want the ability to change the priority of some completion providers based on the context of the completion. The new design allows for providers to increase their priority when they know they have something of high-importance given some piece of contextual knowledge.
Long term, we also want to provide an API for providers to register a small number of suggested completions that will be raised to the top-level, regardless of what provider gave them. This is necessary instead of having global “scoring” since that would require both O(n) scans of the data set as well as coming up with a strategy to score disparate systems (and search engines prove that rarely works well).
More to do
There are still a couple things that I think we should address that may influence the API design more. For example:
- How should we handle string interpolation? A simplified API for completions when working inside of strings might be useful. Think
printf(), etc as potential examples here.
- The upcoming Gtk+ 3.24 release will give us access to the
move_to_rect()API. Combined with some Wayland xdg_popup improvements, this could allow us to make our display widget more flexible.
- Parameter completion is still a bit of an annoying process. We could probably come up with a strategy to make the display look a lot better here.
- Give some tweaks and knobs for how much and what to complete (just function name vs parameters and types).
Rarely do I write any code that doesn’t have bugs. Now that this is landing in Builder Nightly soon, I could use some more testing and bug filing from the community at large.
I’m very happy with the improvements over the past couple of months. Between getting Clang out of process and this to allow us to make clang completion fast, I think we’re in a much better place.
We can’t get this design into older GtkSourceView releases, but we can probably look at some form of integration into what will eventually integrate with Gtk4. I would be very happy if it influenced new API releases of the library so that we don’t need to carry the implementation downstream.
The other day I found myself perusing through some disassembly to get an idea of the code’s complexity. I do that occasionally because I find it the quickest way to determine if something is out of whack.
While I was there, I noticed a rather long
_get_type() function. It looked a bit long and more importantly, I only saw one exit point (
retq instruction on x86_64).
That piqued my interest because
_get_type() functions are expected to be fast. In the fast-path (when they’ve already been registered), I’d expect them to check for a non-zero
static GType type_id and if so return it. That is just a handful of instructions at most, so what gives?
The first thing that came to mind is that I use
-O0 -g -fno-omit-frame-pointer on my local builds so that I get good debug symbols and ensure that Linux-perf can unwind the stack when profiling. So let’s disable that.
Now I’ve got everything rebuilt with the defaults (
-O2 -fomit-frame-pointer). Now I see a couple exit points, but still what appears to be too many for the fast path and what is this
__stack_chk_fail@plt I see?
A quick search yields some information about
-fstack-protector which is a recent (well half-decade) compiler feature that employs various tricks to detect stack corruption. Distributions seem to enable this by default using
-fstack-protector-strong. That tries to only add stack checks to code it thinks is accessing stack allocated data.
So quick recompile with
-fno-stack-protector to disable the security feature and sure enough, a proper fast path emerges, We drop from 15 instructions (with 2 conditional jumps) to 5 instructions (with 1 conditional jump).
So the next question is: “Well okay, is this worth making faster?”
That’s a complicated question. The code was certainly faster before that feature was enabled by default. The more instructions you execute, the more code has to be loaded into the instruction cache (which is very small). To load instructions means pushing others out. So even if the code itself isn’t much faster, it can prevent the surrounding code from being faster.
But furthermore, we do a lot of
_get_type() calls. They are used when doing function precondition checks, virtual methods, signals, type checks, interface lookups, checking a type for conformance, altering the reference count, marshaling data, accessing properties, … you get the idea.
So I mucked through about 5 different ways trying to see if I could make things faster without disabling the stack protector, without much luck. The way types are registered access some local data via macros. Nothing seemed to get me any closer to those magic 5 instructions.
GCC, since version 4.4, has allowed you to disable the stack-protector on a per-function basis. Just add
__attribute__((optimize("no-stack-protector"))) to the function prototype.
#if G_GNUC_CHECK_VERSION(4, 4) # define G_GNUC_NO_STACK_PROTECTOR \ __attribute__((optimize("no-stack-protector"))) #else # define G_GNUC_NO_STACK_PROTECTOR #endif GType foo_get_type (void) G_GNUC_NO_STACK_PROTECTOR;
Now we get back to our old (faster) version of the code.
48 8b 05 b9 15 20 00 mov 0x2015b9(%rip),%rax 48 85 c0 test %rax,%rax 74 0c je 400ac8 48 8b 05 ad 15 20 00 mov 0x2015ad(%rip),%rax c3 retq
But what’s the difference you ask?
I put together a test that I can run on a number of machines. It was unilaterally faster in each case (as expected), but some by as much as 50% (likely due to various caches).
|ARM||Ubuntu 14.04||Odroid X2||+24%|
|x64_64||Fedora 28||X1 Carbon Gen3||+25.5%|
|x86_64||Fedora 28||i7 gen7 NUC||+12.25%|
|x86_64||Fedora 28||Surface Book 2 i7 (gen8)||+12.5%|
|x86_64||Fedora 27||Onda Tablet||+50.6%|
It’s my opinion that one place where it makes sense to keep things very fast (and reduce instruction cache blow-out) is a type system. That code gets run a lot intermixed between all the code you really care about.
GTask is super handy, but it’s important you’re very careful with it when threading is involved. For example, the normal threaded use case might be something like this:
state = g_slice_new0 (State); state->frob = get_frob_state (self); state->baz = get_baz_state (self); task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, state, state_free); g_task_run_in_thread (task, state_worker_func);
The idea here is that you create your state upfront, and pass that state to the worker thread so that you don’t race accessing
self-> fields from multiple threads. The “shared nothing” approach, if you will.
However, even this isn’t safe if
self has thread usage requirements. For example, if
self is a
GtkWidget or some other object that is expected to only be used from the main-thread, there is a chance your object could be finalized in a thread.
task_data you set could also be finalized in the thread. If your task data also holds references to objects which have thread requirements, those too can be unref’d from the thread (thereby cascading through the object graph should you hit this undesirable race).
Such can happen when you call
g_task_return_pointer() or any of the other return variants from the worker thread. That call will queue the result to be dispatched to the
GMainContext that created the task. If your CPU task-switches to that thread before the worker thread has released it’s reference you risk the chance the thread holds the last reference to the task.
In that situation
task_data will both be finalized in that worker thread.
Addressing this in Builder
We already have various thread pools in Builder for work items so it would be nice if we could both fix the issue in our usage as well as unify the thread pools. Additionally, there are cases where it would be nice to “chain task results” to avoid doing duplicate work when two subsystems request the same work to be performed.
So now Builder has
IdeTask which is very similar in API to
GTask but provides some additional guarantees that would be very difficult to introduce back into the
GTask implementation (without breaking semantics). We do this by passing the result and the threads last ownership reference to the
IdeTask back to the
GMainContext at the same time, ensuring the last unref happens in the expected context.
While I was at it, I added a bunch of debugging tools for myself which caught some bugs in my previous usage of
GTask. Bugs were filed,
GTask has been improved, yadda yadda.
But I anticipate the threading situation to remain in
GTask and you should be aware of that if you’re writing new code using
I proposed a change to GObject that was merged not too long ago that uses __typeof__ on GCC/Clang to propagate the pointer type from the parameter. For example, the old prototype was something like:
gpointer g_object_ref (gpointer instance);
Which meant you got a cast for free even if you made a mistake such as:
FooBar *bar = foo_bar_new (); FooFrob *frob = g_object_ref (bar);
But now, we propagate the type of the parameter as the return value with a macro that is essentially:
#define g_object_ref(Obj) ((__typeof__(Obj)) (g_object_ref) (Obj))
Now, the code example above would be an -Wincompatible-pointer-types warning from GCC/Clang. This isn’t a common problem (people often rely on this implicit cast when using inheritance). However, when you do make the mistake it’s rather annoying to track down. I’ve certainly made the mistake before when too-quickly copying lines of code, and I know I’m not the only one.
So, how do you fix the warnings in your code base if you were relying on the implicit cast? Both of these will work, I have a preference for the former.
FooFrob *frob = g_object_ref (FOO_FROB (bar)); FooFrob *frob = FOO_FROB (g_object_ref (bar));
If you’re running Wayland and have a touchpad capable of multi-touch, Builder (Nightly) now lets you do fun stuff like the following video demonstrates.
Just three-finger-swipe left or right to move the document. Content is sticky-to-fingers, which is my expectation when using gestures.
It might also work on a touchscreen, but I haven’t tried.