gpui: Introduce stacker to address stack overflows with deep layout trees (#35813)

Piotr Osiewicz , Anthony Eid , Lukas Wirth , and Ben Kunkle created

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: Ben Kunkle <ben@zed.dev>

Release Notes:

- N/A

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: Ben Kunkle <ben@zed.dev>

Change summary

Cargo.lock                      | 35 +++++++++++++++++++++++++++++++++++
Cargo.toml                      |  1 +
crates/gpui/Cargo.toml          |  1 +
crates/gpui/src/elements/div.rs |  9 +++++++--
crates/gpui/src/taffy.rs        | 15 ++++++++++++---
5 files changed, 56 insertions(+), 5 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7482,6 +7482,7 @@ dependencies = [
  "slotmap",
  "smallvec",
  "smol",
+ "stacksafe",
  "strum 0.27.1",
  "sum_tree",
  "taffy",
@@ -15541,6 +15542,40 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
+[[package]]
+name = "stacker"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "psm",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "stacksafe"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9c1172965d317e87ddb6d364a040d958b40a1db82b6ef97da26253a8b3d090"
+dependencies = [
+ "stacker",
+ "stacksafe-macro",
+]
+
+[[package]]
+name = "stacksafe-macro"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "172175341049678163e979d9107ca3508046d4d2a7c6682bee46ac541b17db69"
+dependencies = [
+ "proc-macro-error2",
+ "quote",
+ "syn 2.0.101",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"

Cargo.toml 🔗

@@ -590,6 +590,7 @@ simplelog = "0.12.2"
 smallvec = { version = "1.6", features = ["union"] }
 smol = "2.0"
 sqlformat = "0.2"
+stacksafe = "0.1"
 streaming-iterator = "0.1"
 strsim = "0.11"
 strum = { version = "0.27.0", features = ["derive"] }

crates/gpui/Cargo.toml 🔗

@@ -119,6 +119,7 @@ serde_json.workspace = true
 slotmap = "1.0.6"
 smallvec.workspace = true
 smol.workspace = true
+stacksafe.workspace = true
 strum.workspace = true
 sum_tree.workspace = true
 taffy = "=0.9.0"

crates/gpui/src/elements/div.rs 🔗

@@ -27,6 +27,7 @@ use crate::{
 use collections::HashMap;
 use refineable::Refineable;
 use smallvec::SmallVec;
+use stacksafe::{StackSafe, stacksafe};
 use std::{
     any::{Any, TypeId},
     cell::RefCell,
@@ -1195,7 +1196,7 @@ pub fn div() -> Div {
 /// A [`Div`] element, the all-in-one element for building complex UIs in GPUI
 pub struct Div {
     interactivity: Interactivity,
-    children: SmallVec<[AnyElement; 2]>,
+    children: SmallVec<[StackSafe<AnyElement>; 2]>,
     prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
     image_cache: Option<Box<dyn ImageCacheProvider>>,
 }
@@ -1256,7 +1257,8 @@ impl InteractiveElement for Div {
 
 impl ParentElement for Div {
     fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
-        self.children.extend(elements)
+        self.children
+            .extend(elements.into_iter().map(StackSafe::new))
     }
 }
 
@@ -1272,6 +1274,7 @@ impl Element for Div {
         self.interactivity.source_location()
     }
 
+    #[stacksafe]
     fn request_layout(
         &mut self,
         global_id: Option<&GlobalElementId>,
@@ -1307,6 +1310,7 @@ impl Element for Div {
         (layout_id, DivFrameState { child_layout_ids })
     }
 
+    #[stacksafe]
     fn prepaint(
         &mut self,
         global_id: Option<&GlobalElementId>,
@@ -1376,6 +1380,7 @@ impl Element for Div {
         )
     }
 
+    #[stacksafe]
     fn paint(
         &mut self,
         global_id: Option<&GlobalElementId>,

crates/gpui/src/taffy.rs 🔗

@@ -3,6 +3,7 @@ use crate::{
 };
 use collections::{FxHashMap, FxHashSet};
 use smallvec::SmallVec;
+use stacksafe::{StackSafe, stacksafe};
 use std::{fmt::Debug, ops::Range};
 use taffy::{
     TaffyTree, TraversePartialTree as _,
@@ -11,8 +12,15 @@ use taffy::{
     tree::NodeId,
 };
 
-type NodeMeasureFn = Box<
-    dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>,
+type NodeMeasureFn = StackSafe<
+    Box<
+        dyn FnMut(
+            Size<Option<Pixels>>,
+            Size<AvailableSpace>,
+            &mut Window,
+            &mut App,
+        ) -> Size<Pixels>,
+    >,
 >;
 
 struct NodeContext {
@@ -88,7 +96,7 @@ impl TaffyLayoutEngine {
             .new_leaf_with_context(
                 taffy_style,
                 NodeContext {
-                    measure: Box::new(measure),
+                    measure: StackSafe::new(Box::new(measure)),
                 },
             )
             .expect(EXPECT_MESSAGE)
@@ -143,6 +151,7 @@ impl TaffyLayoutEngine {
         Ok(edges)
     }
 
+    #[stacksafe]
     pub fn compute_layout(
         &mut self,
         id: LayoutId,