fix: workspace hotpatch cache thrashing, feat: add compile profiling, cleanup + refactor + document cli request.rs file (#5479)

* it works!

* fix wasm hotpatch error

* bump wasmopt

* rename wasmopt flag

* rename ldd_keep_exports

* add build profiling

* undo bundle ignore

* some cleanups

* more cleanups

* wip: hoist up telemetry to builder

* fix compile

* wip: flamegraph rewrite

* clean up flamegraph impl

* cleanup profiling engine

* add comment on spans obj

* cleanup more phases

* more cleanup

* small cleanups

* bit more cleanups

* cleanup phases

* sign with tokio, not std proc

* split out the specific platform helpers

* cleanup a bit

* more cleanups

* more cleanups

* more cleanups

* more cleanups

* pull out fingerprint logic

* wip... I think I have a slightly better approach. saving checkpoint

* hm I think it's better now?

* fix: also seems to work not in workspace mode juuust yet

* okay yay almost there

* clooose but not there yet

* fix: manually export __wasm_apply_data_relocs since got relocations weren't firing

WASM workspace hotpatching: export __wasm_apply_global_relocs to fix call_indirect type mismatch

When workspace crates are recompiled with -Crelocation-model=pic for hotpatching, wasm-ld generates GOT.func.internal globals that hold element-segment-relative offsets (e.g., MetricCardProps::memoize = 31). These need __table_base added at runtime by __wasm_apply_global_relocs to become absolute table indices.

wasm-ld generates __wasm_apply_global_relocs but treats it as an internal synthetic function -- neither --export nor --export-if-defined will export it. Without the export, the runtime's call silently failed (the old code used unchecked_into on undefined), leaving the globals unrelocated. Function pointers then pointed to index 31 in the original module's table (some unrelated function) instead of table_base + 31 (the patched function), causing call_indirect to a signature that does not match.

Three changes:

patch.rs: After walrus processing, find __wasm_apply_global_relocs by name and add it as an export before emitting the patched wasm.
lib.rs: Replace unchecked_into with dyn_into for calling relocation functions, so missing exports are handled gracefully instead of silently swallowed.
link.rs: Remove the ineffective --export-if-defined flags that were added during debugging.

* cleanup

* fix scope dir

* change up naming a bit

* more tidying up

* more cleanups

* ignore early return

* clean er up

* more comprehensive cache bustign

* bust fingerprints of dag

* move bust_fingerprint back to request.rs

* add better rustdoc

* more idiomatic

* more cleanups

* fix panic, typos

* more lints

* only delete workspace prints in fat mod

* clippy
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2026-04-14 12:09:49 -07:00
1 parent 348208d1cb
commit fb855b7088
37 files changed
+7276 -6865

No files matched your search

+11 -11
View File
@@ -159,6 +159,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Resu
}
}
BuilderUpdate::Progress { .. } => {}
BuilderUpdate::ProfilePhase { .. } => {}
BuilderUpdate::CompilerMessage { message } => {
screen.push_cargo_log(message);
}
@@ -248,24 +249,23 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Resu
screen.push_log(log);
}
ServeUpdate::OpenApp => match builder.use_hotpatch_engine {
true if !matches!(builder.client.build.bundle, BundleFormat::Web) => {
ServeUpdate::OpenApp => {
if builder.use_hotpatch_engine
&& !matches!(builder.client.build.bundle, BundleFormat::Web)
{
tracing::warn!(
"Opening a native app with hotpatching enabled requires a full rebuild..."
);
builder.full_rebuild().await;
devserver.send_reload_start().await;
devserver.start_build().await;
} else if let Err(err) = builder.open_all(&devserver, true).await {
tracing::error!(
"Failed to open app: {}",
crate::error::log_stacktrace(&err, 15)
)
}
_ => {
if let Err(err) = builder.open_all(&devserver, true).await {
tracing::error!(
"Failed to open app: {}",
crate::error::log_stacktrace(&err, 15)
)
}
}
},
}
ServeUpdate::Redraw => {
// simply returning will cause a redraw
+13 -12
View File
@@ -259,12 +259,12 @@ impl AppServer {
if !self.ssg || server.stage != BuildStage::Success {
return;
}
if let Err(err) = crate::pre_render_static_routes(
Some(devserver.devserver_address()),
server,
Some(&server.tx.clone()),
)
.await
if let Err(err) = server
.pre_render_static_routes(
Some(devserver.devserver_address()),
Some(&server.tx.clone()),
)
.await
{
tracing::error!("Failed to pre-render static routes: {err}");
}
@@ -362,7 +362,7 @@ impl AppServer {
// This prevents losing changes from tools like stylance, tailwind, or sass that generate files
// in response to source changes.
tracing::debug!(
"Queueing file change: client is not ready to receive hotreloads. Files: {:#?}",
"Queueing file change - client is not ready to receive hotreloads. Files: {:?}",
files
);
self.pending_file_changes.extend(files.iter().cloned());
@@ -410,7 +410,6 @@ impl AppServer {
// Get the cached file if it exists - ignoring if it doesn't exist
let Some(cached_file) = self.file_map.get_mut(path) else {
tracing::debug!("No entry for file in filemap: {:?}", path);
tracing::debug!("Filemap: {:#?}", self.file_map.keys());
continue;
};
@@ -601,10 +600,12 @@ impl AppServer {
use crate::cli::styles::GLOW_STYLE;
if should_open {
let time_taken = artifacts
.time_end
.duration_since(artifacts.time_start)
.unwrap();
let time_taken = self.client.total_build_time().unwrap_or_else(|| {
artifacts
.time_end
.duration_since(artifacts.time_start)
.unwrap()
});
if self.client.builds_opened == 0 {
tracing::info!(
+1
View File
@@ -265,6 +265,7 @@ impl WebServer {
BuilderUpdate::StderrReceived { .. } => {}
BuilderUpdate::ProcessExited { .. } => {}
BuilderUpdate::ProcessWaitFailed { .. } => {}
BuilderUpdate::ProfilePhase { .. } => {}
}
}