diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 0472b84436bd51..f02d14d90e9e17 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -21,7 +21,6 @@ env: NAPI_CLI_VERSION: 2.18.4 TURBO_VERSION: 2.8.11 NODE_LTS_VERSION: 20 - CARGO_PROFILE_RELEASE_LTO: 'true' TURBO_TEAM: 'vercel' TURBO_CACHE: 'remote:rw' # Without this environment variable, rust-lld will fail because some dependencies defaults to newer version of macOS by default. diff --git a/Cargo.lock b/Cargo.lock index 59ac9cb1517cdd..77b0c1781ddb0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9513,6 +9513,7 @@ dependencies = [ "inventory", "once_cell", "parking_lot", + "phf", "pin-project-lite", "rayon", "regex", diff --git a/Cargo.toml b/Cargo.toml index 19aa05e9ef8be5..9d1218a8a4f030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ opt-level = 1 opt-level = 3 [profile.release] +lto = "thin" [profile.release.package] @@ -433,6 +434,7 @@ owo-colors = "4.2.2" parcel_selectors = "0.28.2" parking_lot = "0.12.1" pathdiff = "0.2.1" +phf = { version = "0.11", features = ["macros"] } petgraph = "0.8.3" pin-project-lite = "0.2.9" postcard = "1.0.4" diff --git a/crates/next-napi-bindings/Cargo.toml b/crates/next-napi-bindings/Cargo.toml index b49382f11b668b..600f1bf59efd0e 100644 --- a/crates/next-napi-bindings/Cargo.toml +++ b/crates/next-napi-bindings/Cargo.toml @@ -5,7 +5,7 @@ version = "0.0.0" publish = false [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [features] # Instead of enabling all the plugin-related functionality by default, make it diff --git a/lerna.json b/lerna.json index ced1cca475ea7a..c59dfeb09efed9 100644 --- a/lerna.json +++ b/lerna.json @@ -15,5 +15,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "16.2.0-canary.98" + "version": "16.2.0-canary.99" } \ No newline at end of file diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index a313306467d138..bb46ebfed8980e 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 65d83662c7a61f..47d9c9ff5a3ec0 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "ESLint configuration used by Next.js.", "license": "MIT", "repository": { @@ -12,7 +12,7 @@ "dist" ], "dependencies": { - "@next/eslint-plugin-next": "16.2.0-canary.98", + "@next/eslint-plugin-next": "16.2.0-canary.99", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 1e50019ed35550..0faf72dd125d8e 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index acd17c339a5323..93e6e9d3367a5a 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index 40cc8fb449ff7d..2717c76980885e 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index d33d26e53026f5..28f44d39fcf352 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index f5946e793fc7fc..1244556db054fa 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 3f6a6b4bd50482..09a1291ca608b5 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 3aaedf83c30bb5..02e64542976748 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-playwright/package.json b/packages/next-playwright/package.json index 1f3e5309c81290..95b02ce4d95e35 100644 --- a/packages/next-playwright/package.json +++ b/packages/next-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@next/playwright", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "repository": { "url": "vercel/next.js", "directory": "packages/next-playwright" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 9a2d89ece616c6..23c24564ab5392 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index a29de2ed237be3..4ef4dba304203c 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index dc83bbc8a80d10..4ddacf44fc7c12 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-routing/package.json b/packages/next-routing/package.json index 0609939b44d6af..df722d19060115 100644 --- a/packages/next-routing/package.json +++ b/packages/next-routing/package.json @@ -1,6 +1,6 @@ { "name": "@next/routing", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "keywords": [ "react", "next", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index 7f898be6112ee1..be1ddbd73101f6 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 9a044c00f58aed..f4921ef77a98a2 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "private": true, "files": [ "native/" diff --git a/packages/next/package.json b/packages/next/package.json index 935a44594b27a5..623f80217f6017 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -97,7 +97,7 @@ ] }, "dependencies": { - "@next/env": "16.2.0-canary.98", + "@next/env": "16.2.0-canary.99", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -161,11 +161,11 @@ "@modelcontextprotocol/sdk": "1.18.1", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/font": "16.2.0-canary.98", - "@next/polyfill-module": "16.2.0-canary.98", - "@next/polyfill-nomodule": "16.2.0-canary.98", - "@next/react-refresh-utils": "16.2.0-canary.98", - "@next/swc": "16.2.0-canary.98", + "@next/font": "16.2.0-canary.99", + "@next/polyfill-module": "16.2.0-canary.99", + "@next/polyfill-nomodule": "16.2.0-canary.99", + "@next/react-refresh-utils": "16.2.0-canary.99", + "@next/swc": "16.2.0-canary.99", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.51.1", "@rspack/core": "1.6.7", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index cee743e546180e..be2654108625fd 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index 5f828f968e43c9..63f7e51b65ffec 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "16.2.0-canary.98", + "version": "16.2.0-canary.99", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -26,7 +26,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "16.2.0-canary.98", + "next": "16.2.0-canary.99", "outdent": "0.8.0", "prettier": "2.5.1", "typescript": "5.9.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93d91e96b04cee..b559c2a9ac8ff7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1017,7 +1017,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../eslint-plugin-next eslint: specifier: '>=9.0.0' @@ -1094,7 +1094,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../next-env '@swc/helpers': specifier: 0.5.15 @@ -1219,19 +1219,19 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/font': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../font '@next/polyfill-module': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../react-refresh-utils '@next/swc': - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1956,7 +1956,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 16.2.0-canary.98 + specifier: 16.2.0-canary.99 version: link:../next outdent: specifier: 0.8.0 diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 7c3182a912cf3e..b6d328497bb2b7 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -950,7 +950,7 @@ impl TurboTasksBackendInner { let _span = tracing::trace_span!( "recomputation", - cell_type = get_value_type(cell.type_id).global_name, + cell_type = get_value_type(cell.type_id).ty.global_name, cell_index = cell.index ) .entered(); @@ -3615,7 +3615,11 @@ impl DebugTraceTransientTask { cell_type_id: Option, ) -> fmt::Result { if let Some(ty) = cell_type_id { - write!(f, " (read cell of type {})", get_value_type(ty).global_name) + write!( + f, + " (read cell of type {})", + get_value_type(ty).ty.global_name + ) } else { Ok(()) } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs index 48df6e1ff4ed54..a1c765d270d921 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs @@ -137,13 +137,13 @@ impl std::fmt::Display for TaskDirtyCauseInContext<'_> { write!( f, "{} cell changed", - turbo_tasks::registry::get_value_type(*value_type).name + turbo_tasks::registry::get_value_type(*value_type).ty.name ) } else { write!( f, "{} cell changed (keys: {})", - turbo_tasks::registry::get_value_type(*value_type).name, + turbo_tasks::registry::get_value_type(*value_type).ty.name, keys.iter() .map(|key| match key { Some(k) => k.to_string(), @@ -158,7 +158,7 @@ impl std::fmt::Display for TaskDirtyCauseInContext<'_> { write!( f, "{} cell removed", - turbo_tasks::registry::get_value_type(*value_type).name + turbo_tasks::registry::get_value_type(*value_type).ty.name ) } TaskDirtyCause::OutputChange { .. } => { @@ -168,7 +168,7 @@ impl std::fmt::Display for TaskDirtyCauseInContext<'_> { write!( f, "{} collectible changed", - turbo_tasks::registry::get_trait(*collectible_type).name + turbo_tasks::registry::get_trait(*collectible_type).ty.name ) } TaskDirtyCause::Invalidator => write!(f, "invalidator"), diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index 786b03afb5e742..427820b6e0c7a8 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -89,7 +89,9 @@ impl UpdateCellOperation { && content != task.get_cell_data(is_serializable_cell_content, cell) { let task_description = task.get_task_description(); - let cell_type = turbo_tasks::registry::get_value_type(cell.type_id).global_name; + let cell_type = turbo_tasks::registry::get_value_type(cell.type_id) + .ty + .global_name; eprintln!( "Task {} updated cell #{} (type: {}) while recomputing", task_description, cell.index, cell_type diff --git a/turbopack/crates/turbo-tasks-macros/src/func.rs b/turbopack/crates/turbo-tasks-macros/src/func.rs index 4de442be75c0be..0a8e687f5e9daf 100644 --- a/turbopack/crates/turbo-tasks-macros/src/func.rs +++ b/turbopack/crates/turbo-tasks-macros/src/func.rs @@ -1131,14 +1131,11 @@ impl NativeFn { quote! { { #[allow(deprecated)] - const IMPL: &dyn turbo_tasks::task::TaskFn = - const { &#task_fn as &dyn turbo_tasks::task::TaskFn }; - #[allow(deprecated)] turbo_tasks::macro_helpers::NativeFunction::new( #function_path_string, #function_global_name, #arg_meta, - IMPL, + &#task_fn, #is_root, ) } diff --git a/turbopack/crates/turbo-tasks-macros/src/function_macro.rs b/turbopack/crates/turbo-tasks-macros/src/function_macro.rs index 21d2aca1314201..fe0c2e87158c53 100644 --- a/turbopack/crates/turbo-tasks-macros/src/function_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/function_macro.rs @@ -82,12 +82,9 @@ pub fn function(args: TokenStream, input: TokenStream) -> TokenStream { #[doc(hidden)] #inline_signature #inline_block - static #native_function_ident: #native_function_ty = #native_function_def; - - // Register the function for deserialization - turbo_tasks::macro_helpers::inventory_submit! { - turbo_tasks::macro_helpers::CollectableFunction(&#native_function_ident) - } + turbo_tasks::macro_helpers::turbo_register!( + #native_function_ident: #native_function_ty = #native_function_def + ); #(#errors)* } diff --git a/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs index ca19f1a94058f7..fe3d6b58f16c82 100644 --- a/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs @@ -149,12 +149,9 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { pub(self) #inline_signature #inline_block } - static #native_function_ident: #native_function_ty = #native_function_def; - - // Register the function for deserialization - turbo_tasks::macro_helpers::inventory_submit! { - turbo_tasks::macro_helpers::CollectableFunction(&#native_function_ident) - } + turbo_tasks::macro_helpers::turbo_register!( + #native_function_ident: #native_function_ty = #native_function_def + ); }) } @@ -276,16 +273,14 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { #inline_signature #inline_block } - static #native_function_ident: #native_function_ty = #native_function_def; - - // Register the function for deserialization - turbo_tasks::macro_helpers::inventory_submit! { - turbo_tasks::macro_helpers::CollectableFunction(&#native_function_ident) - } + turbo_tasks::macro_helpers::turbo_register!( + #native_function_ident: #native_function_ty = #native_function_def + ); }); + let method_name_str = syn::LitStr::new(&ident.to_string(), ident.span()); trait_methods.push(quote! { - (stringify!(#ident), &#native_function_ident), + (#method_name_str, &#native_function_ident) }); } } @@ -296,15 +291,16 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { // 2 TraitTypes (requires functions) // 3 ValueTypes (requires functions and TraitTypeIds) // 4.VTableRegistries (requires ValueTypeIds) - turbo_tasks::macro_helpers::inventory_submit!{ - turbo_tasks::macro_helpers::CollectableTraitMethods( - || ( - ::std::any::TypeId::of::<#ty>(), - <::std::boxed::Box as turbo_tasks::VcValueTrait>::get_trait_type_id(), - vec![#(#trait_methods)*] - ) - ) - } + turbo_tasks::macro_helpers::inventory_submit!{{ + const LEN: usize = <::std::boxed::Box as turbo_tasks::macro_helpers::TraitVtablePrototype>::LEN; + static METHODS: [&turbo_tasks::macro_helpers::NativeFunction; LEN] = turbo_tasks::macro_helpers::build_trait_vtable::<::std::boxed::Box, LEN>(&[#(#trait_methods),*]); + + turbo_tasks::macro_helpers::CollectableTraitMethods { + value_type: <#ty as turbo_tasks::macro_helpers::RegistryDef::>::DEF, + trait_type: <::std::boxed::Box as turbo_tasks::macro_helpers::RegistryDef::>::DEF, + methods: &METHODS, + } + }} // These can execute later so they can reference trait_types during registration diff --git a/turbopack/crates/turbo-tasks-macros/src/value_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_macro.rs index 34f0c9cc455f20..2d08f09606a442 100644 --- a/turbopack/crates/turbo-tasks-macros/src/value_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/value_macro.rs @@ -425,19 +425,9 @@ pub fn value_type_and_register( }; quote! { - static #value_type_ident: turbo_tasks::macro_helpers::Lazy = - turbo_tasks::macro_helpers::Lazy::new(|| { - let mut value_type = #new_value_type; - turbo_tasks::macro_helpers::register_trait_methods( - ::std::any::TypeId::of::<#ty>(), - &mut value_type, - ); - value_type - }); - - turbo_tasks::macro_helpers::inventory_submit! { - turbo_tasks::macro_helpers::CollectableValueType(&#value_type_ident) - } + turbo_tasks::macro_helpers::turbo_register!( + #ty => #value_type_ident: turbo_tasks::ValueType = #new_value_type + ); #[automatically_derived] unsafe impl #impl_generics turbo_tasks::VcValueType for #ty #where_clause { @@ -445,12 +435,7 @@ pub fn value_type_and_register( type CellMode = #cell_mode; fn get_value_type_id() -> turbo_tasks::ValueTypeId { - static ident: turbo_tasks::macro_helpers::Lazy = - turbo_tasks::macro_helpers::Lazy::new(|| { - turbo_tasks::registry::get_value_type_id(&#value_type_ident) - }); - - *ident + turbo_tasks::registry::get_value_type_id(&#value_type_ident) } fn has_serialization() -> bool { diff --git a/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs index dde9a96a70ff0b..dd83dc8cac7e35 100644 --- a/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs @@ -69,6 +69,8 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { let trait_type_ident = get_trait_type_ident(trait_ident); let mut dynamic_trait_fns = Vec::new(); let mut trait_methods: Vec = Vec::new(); + let mut method_names: Vec = Vec::new(); + let mut default_methods: Vec = Vec::new(); let mut native_functions = Vec::new(); let mut items: Vec = Vec::with_capacity(raw_items.len()); let mut errors = Vec::new(); @@ -218,9 +220,19 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { let native_function_ty = native_function.ty(); let native_function_def = native_function.definition(); + let method_name_str = syn::LitStr::new(&ident.to_string(), ident.span()); + let index = trait_methods.len() as u8; trait_methods.push(quote! { - (stringify!(#ident), Some(&#native_function_ident)), + #method_name_str => turbo_tasks::TraitMethod { + trait_type: &#trait_type_ident, + trait_name: stringify!(#trait_ident), + method_name: #method_name_str, + default_method: Some(&#native_function_ident), + index: #index, + }, }); + method_names.push(quote! { #method_name_str }); + default_methods.push(quote! { Some(&#native_function_ident) }); native_functions.push(quote! { #[doc(hidden)] @@ -239,19 +251,26 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { #inline_signature #inline_block } - static #native_function_ident: #native_function_ty = #native_function_def; - - // Register the function for deserialization - turbo_tasks::macro_helpers::inventory_submit! { - turbo_tasks::macro_helpers::CollectableFunction(&#native_function_ident) - } + turbo_tasks::macro_helpers::turbo_register!( + #native_function_ident: #native_function_ty = #native_function_def + ); }); turbo_fn.static_block(&native_function_ident) } else { + let method_name_str = syn::LitStr::new(&ident.to_string(), ident.span()); + let index = trait_methods.len() as u8; trait_methods.push(quote! { - (stringify!(#ident), None), + #method_name_str => turbo_tasks::TraitMethod { + trait_type: &#trait_type_ident, + trait_name: stringify!(#trait_ident), + method_name: #method_name_str, + default_method: None, + index: #index, + }, }); + method_names.push(quote! { #method_name_str }); + default_methods.push(quote! { None }); quote! { ; } }; @@ -289,6 +308,7 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { extended_supertraits.push(quote!(turbo_tasks::debug::ValueDebug)); } + let num_methods = method_names.len(); let trait_name = global_name_for_type(quote! { dyn #trait_ident }); let expanded = quote! { #[must_use] @@ -300,17 +320,25 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { #(#native_functions)* - static #trait_type_ident: turbo_tasks::macro_helpers::Lazy = - turbo_tasks::macro_helpers::Lazy::new(|| { - turbo_tasks::TraitType::new( + turbo_tasks::macro_helpers::turbo_register!( + Box => #trait_type_ident: turbo_tasks::TraitType = { + use turbo_tasks::macro_helpers::{phf, phf::phf_map}; + turbo_tasks::TraitType::new::<&'static dyn #trait_ident>( stringify!(#trait_ident), #trait_name, - vec![#(#trait_methods)*]) - }); + phf_map! { + #(#trait_methods)* + }, + &[#(#method_names),*], + &[#(#default_methods),*] + ) + } + ); - // Register the trait for deserialization - turbo_tasks::macro_helpers::inventory_submit! { - turbo_tasks::macro_helpers::CollectableTrait(&#trait_type_ident) + impl turbo_tasks::macro_helpers::TraitVtablePrototype for Box { + const LEN: usize = #num_methods; + const NAMES: &[&str] = &[#(#method_names),*]; + const DEFAULTS: &[Option<&turbo_tasks::macro_helpers::NativeFunction>] = &[#(#default_methods),*]; } #[automatically_derived] @@ -318,14 +346,10 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { type ValueTrait = dyn #trait_ident; fn get_trait_type_id() -> turbo_tasks::TraitTypeId { - static ident: turbo_tasks::macro_helpers::Lazy = - turbo_tasks::macro_helpers::Lazy::new(|| { - turbo_tasks::registry::get_trait_type_id(&#trait_type_ident) - }); - - *ident + turbo_tasks::registry::get_trait_type_id(&#trait_type_ident) } + // TODO: Remove this Lazy VTableRegistry once trait resolution is fully migrated fn get_impl_vtables() -> &'static turbo_tasks::macro_helpers::VTableRegistry { static registry: turbo_tasks::macro_helpers::Lazy> = turbo_tasks::macro_helpers::Lazy::new(|| turbo_tasks::macro_helpers::VTableRegistry::new(turbo_tasks::registry::get_trait_type_id(&#trait_type_ident))); diff --git a/turbopack/crates/turbo-tasks/Cargo.toml b/turbopack/crates/turbo-tasks/Cargo.toml index f0ea87d4ee103a..11d59f22483fcb 100644 --- a/turbopack/crates/turbo-tasks/Cargo.toml +++ b/turbopack/crates/turbo-tasks/Cargo.toml @@ -37,6 +37,7 @@ indexmap = { workspace = true, features = ["serde"] } inventory = { workspace = true } once_cell = { workspace = true } parking_lot = { workspace = true, features = ["serde"]} +phf = { workspace = true } pin-project-lite = { workspace = true } rayon = { workspace = true } regex = { workspace = true } diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index c86e43be2a4e30..97c9f5be63b239 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -77,10 +77,10 @@ pub struct CachedTaskType { } impl CachedTaskType { - /// Get the name of the function from the registry. Equivalent to the + /// Get the name of the function. Equivalent to the /// [`Display`]/[`ToString::to_string`] implementation, but does not allocate a [`String`]. pub fn get_name(&self) -> &'static str { - self.native_fn.name + self.native_fn.ty.name } /// Encodes this task type directly to a hasher, avoiding buffer allocation. diff --git a/turbopack/crates/turbo-tasks/src/macro_helpers.rs b/turbopack/crates/turbo-tasks/src/macro_helpers.rs index ed9aeee8ad937b..60c78aa164a389 100644 --- a/turbopack/crates/turbo-tasks/src/macro_helpers.rs +++ b/turbopack/crates/turbo-tasks/src/macro_helpers.rs @@ -1,16 +1,16 @@ //! Runtime helpers for [turbo-tasks-macro]. -use std::any::TypeId; - pub use async_trait::async_trait; pub use bincode; +pub use inventory; pub use once_cell::sync::{Lazy, OnceCell}; +pub use phf; use rustc_hash::FxHashMap; pub use shrink_to_fit; pub use tracing; use crate::{ - FxDashMap, NonLocalValue, RawVc, TaskInput, TaskPersistence, TraitTypeId, ValueType, + FxDashMap, NonLocalValue, RawVc, TaskInput, TaskPersistence, TraitType, TraitTypeId, ValueType, ValueTypeId, debug::ValueDebugFormatString, }; pub use crate::{ @@ -19,10 +19,12 @@ pub use crate::{ magic_any::MagicAny, manager::{find_cell_by_id, find_cell_by_type, spawn_detached_for_testing}, native_function::{ - ArgMeta, CollectableFunction, NativeFunction, downcast_args_owned, downcast_args_ref, + ArgMeta, NativeFunction, VTABLE_DEFAULT, downcast_args_owned, downcast_args_ref, }, + registry::RegistryDef, task::function::{into_task_fn, into_task_fn_with_this}, - value_type::{CollectableTrait, CollectableValueType}, + turbo_register, + value_type::{TraitVtablePrototype, build_trait_vtable}, }; #[inline(never)] @@ -229,46 +231,12 @@ pub struct CollectableTraitCastFunctions( unsafe impl Sync for CollectableTraitCastFunctions {} inventory::collect! {CollectableTraitCastFunctions} -#[allow(clippy::type_complexity)] -pub struct CollectableTraitMethods( - pub fn() -> ( - TypeId, - TraitTypeId, - Vec<(&'static str, &'static NativeFunction)>, - ), -); -inventory::collect!(CollectableTraitMethods); - -// Called when initializing ValueTypes by value_impl -pub fn register_trait_methods(type_id: TypeId, value_type: &mut ValueType) { - #[allow(clippy::type_complexity)] - static TRAIT_METHODS_BY_VALUE: Lazy< - FxDashMap)>>, - > = Lazy::new(|| { - let map: FxDashMap> = FxDashMap::default(); - for CollectableTraitMethods(thunk) in inventory::iter:: { - let (type_id, trait_type_id, fn_items) = thunk(); - map.entry(type_id) - .or_default() - .push((trait_type_id, fn_items)); - } - map - }); - match TRAIT_METHODS_BY_VALUE.remove(&type_id) { - Some((_, traits)) => { - for (trait_type_id, methods) in traits { - let trait_type = crate::registry::get_trait(trait_type_id); - value_type.register_trait(trait_type_id); - for (name, method) in methods { - value_type.register_trait_method(trait_type.get(name), method); - } - } - } - None => { - // do nothing, values don't have to implement any traits - } - } +pub struct CollectableTraitMethods { + pub value_type: &'static ValueType, + pub trait_type: &'static TraitType, + pub methods: &'static [&'static NativeFunction], } +inventory::collect! {CollectableTraitMethods} /// Submit an item to the inventory. /// diff --git a/turbopack/crates/turbo-tasks/src/native_function.rs b/turbopack/crates/turbo-tasks/src/native_function.rs index 7fdc2fce562c47..4cbc1c8f3daab4 100644 --- a/turbopack/crates/turbo-tasks/src/native_function.rs +++ b/turbopack/crates/turbo-tasks/src/native_function.rs @@ -1,4 +1,4 @@ -use std::{any::Any, fmt::Debug, hash::Hash, pin::Pin}; +use std::{any::Any, fmt::Debug, pin::Pin}; use anyhow::Result; use bincode::{Decode, Encode}; @@ -9,7 +9,9 @@ use turbo_tasks_hash::DeterministicHasher; use crate::{ RawVc, TaskExecutionReason, TaskInput, TaskPersistence, TaskPriority, + macro_helpers::into_task_fn, magic_any::{MagicAny, any_as_encode}, + registry::{RegistryType, turbo_registry}, task::{TaskFn, TaskFnInputs, function::NativeTaskFuture}, }; @@ -180,17 +182,13 @@ pub fn downcast_args_ref(args: &dyn MagicAny) -> &T { /// A native (rust) turbo-tasks function. It's used internally by /// `#[turbo_tasks::function]`. pub struct NativeFunction { - /// A readable name of the function that is used to reporting purposes. - pub(crate) name: &'static str, - pub(crate) arg_meta: ArgMeta, /// The functor that creates a functor from inputs. The inner functor /// handles the task execution. pub(crate) implementation: &'static dyn TaskFn, - // The globally unique name for this function, used when persisting. - pub(crate) global_name: &'static str, + pub(crate) ty: RegistryType, /// Whether this function's tasks should be treated as root nodes in the aggregation graph. /// Root tasks start with aggregation number `u32::MAX` on initial creation. @@ -200,23 +198,38 @@ pub struct NativeFunction { impl Debug for NativeFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("NativeFunction") - .field("name", &self.name) - .field("global_name", &self.global_name) + .field("name", &self.ty.name) + .field("global_name", &self.ty.global_name) .finish_non_exhaustive() } } +fn default_fn() { + panic!("Pure virtual function called") +} + +/// Sentinel used as placeholder in trait vtables before overrides are applied. +/// A single static (not per-monomorphization) avoids bloating the FUNCTIONS registry. +pub static VTABLE_DEFAULT: NativeFunction = NativeFunction::DEFAULT; + impl NativeFunction { - pub const fn new( + #[allow(clippy::declare_interior_mutable_const)] // Interior mutability from RegistryType::id is only written during init + pub const DEFAULT: NativeFunction = NativeFunction { + arg_meta: ArgMeta::new::<()>(), + implementation: &into_task_fn(default_fn) as &dyn TaskFn, + ty: RegistryType::new::<()>("", ""), + is_root: false, + }; + + pub const fn new( name: &'static str, global_name: &'static str, arg_meta: ArgMeta, - implementation: &'static dyn TaskFn, + implementation: &'static T, is_root: bool, ) -> Self { Self { - name, - global_name, + ty: RegistryType::new::(name, global_name), arg_meta, implementation, is_root, @@ -243,7 +256,7 @@ impl NativeFunction { }; tracing::trace_span!( "turbo_tasks::function", - name = self.name, + name = self.ty.name, priority = %priority, flags = flags, reason = reason.as_str() @@ -251,37 +264,8 @@ impl NativeFunction { } pub fn resolve_span(&'static self, priority: TaskPriority) -> Span { - tracing::trace_span!("turbo_tasks::resolve_call", name = self.name, priority = %priority) - } -} -impl PartialEq for NativeFunction { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - -impl Eq for NativeFunction {} -impl Hash for NativeFunction { - fn hash(&self, state: &mut H) { - (self as *const NativeFunction).hash(state); + tracing::trace_span!("turbo_tasks::resolve_call", name = self.ty.name, priority = %priority) } } -impl PartialOrd for &'static NativeFunction { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for &'static NativeFunction { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - Ord::cmp( - &(*self as *const NativeFunction), - &(*other as *const NativeFunction), - ) - } -} - -pub struct CollectableFunction(pub &'static NativeFunction); - -inventory::collect! {CollectableFunction} +turbo_registry!("Function", NativeFunction); diff --git a/turbopack/crates/turbo-tasks/src/raw_vc.rs b/turbopack/crates/turbo-tasks/src/raw_vc.rs index 44b340372301a6..5230c3dd9d4d2c 100644 --- a/turbopack/crates/turbo-tasks/src/raw_vc.rs +++ b/turbopack/crates/turbo-tasks/src/raw_vc.rs @@ -36,7 +36,7 @@ impl Display for CellId { write!( f, "{}#{}", - registry::get_value_type(self.type_id).name, + registry::get_value_type(self.type_id).ty.name, self.index ) } diff --git a/turbopack/crates/turbo-tasks/src/registry.rs b/turbopack/crates/turbo-tasks/src/registry.rs deleted file mode 100644 index 62fcbbd1ada5e8..00000000000000 --- a/turbopack/crates/turbo-tasks/src/registry.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::num::NonZeroU16; - -use anyhow::Error; -use once_cell::sync::Lazy; -use rustc_hash::FxHashMap; - -use crate::{ - TraitType, ValueType, - id::{FunctionId, TraitTypeId, ValueTypeId}, - macro_helpers::CollectableFunction, - native_function::NativeFunction, - value_type::{CollectableTrait, CollectableValueType}, -}; - -/// A trait for types that can be registered in a registry. -/// -/// This allows the generic registry to work with different types -/// while maintaining their specific requirements. -trait RegistryItem: 'static + Eq + std::hash::Hash { - /// The ID type used for this registry item - type Id: Copy + From + std::ops::Deref + std::fmt::Display; - const TYPE_NAME: &'static str; - - /// Get the global name used for sorting and uniqueness validation - fn global_name(&self) -> &'static str; -} - -impl RegistryItem for NativeFunction { - type Id = FunctionId; - const TYPE_NAME: &'static str = "Function"; - - fn global_name(&self) -> &'static str { - self.global_name - } -} - -impl RegistryItem for ValueType { - type Id = ValueTypeId; - const TYPE_NAME: &'static str = "Value"; - - fn global_name(&self) -> &'static str { - self.global_name - } -} - -impl RegistryItem for TraitType { - type Id = TraitTypeId; - const TYPE_NAME: &'static str = "Trait"; - fn global_name(&self) -> &'static str { - self.global_name - } -} - -/// A generic registry that maps between IDs and static references to items. -/// -/// This eliminates the code duplication between Functions, Values, and Traits registries. -struct Registry { - id_to_item: Box<[&'static T]>, - item_to_id: FxHashMap<&'static T, T::Id>, -} - -impl Registry { - /// Create a new registry from a collection of items. - /// - /// Items are sorted by global_name to ensure stable ID assignment. - fn new_from_items(mut items: Vec<&'static T>) -> Self { - // Sort by global name to get stable order - items.sort_unstable_by_key(|item| item.global_name()); - - let mut item_to_id = FxHashMap::with_capacity_and_hasher(items.len(), Default::default()); - - let mut id = NonZeroU16::MIN; - let mut prev_name: Option<&str> = None; - for &item in items.iter() { - let global_name = item.global_name(); - if let Some(prev) = prev_name { - assert!( - prev != global_name, - "multiple {ty} items registered with name: {global_name}!", - ty = T::TYPE_NAME - ); - } - prev_name = Some(global_name); - item_to_id.insert(item, id.into()); - id = id.checked_add(1).expect("overflowing item ids"); - } - - Self { - id_to_item: items.into_boxed_slice(), - item_to_id, - } - } - - /// Get an item by its ID - fn get_item(&self, id: T::Id) -> &'static T { - self.id_to_item[*id as usize - 1] - } - - /// Get the ID for an item - fn get_id(&self, item: &'static T) -> T::Id { - match self.item_to_id.get(&item) { - Some(id) => *id, - None => panic!( - "{ty} isn't registered: {item}", - ty = T::TYPE_NAME, - item = item.global_name() - ), - } - } - - /// Validate that an ID is within the valid range - fn validate_id(&self, id: T::Id) -> Option { - let len = self.id_to_item.len(); - if *id as usize <= len { - None - } else { - Some(anyhow::anyhow!( - "Invalid {ty} id, {id} expected a value <= {len}", - ty = T::TYPE_NAME - )) - } - } -} - -static FUNCTIONS: Lazy> = Lazy::new(|| { - let functions = inventory::iter:: - .into_iter() - .map(|c| c.0) - .collect::>(); - Registry::new_from_items(functions) -}); - -pub fn get_native_function(id: FunctionId) -> &'static NativeFunction { - FUNCTIONS.get_item(id) -} - -pub fn get_function_id(func: &'static NativeFunction) -> FunctionId { - FUNCTIONS.get_id(func) -} - -pub fn validate_function_id(id: FunctionId) -> Option { - FUNCTIONS.validate_id(id) -} - -static VALUES: Lazy> = Lazy::new(|| { - // Inventory does not guarantee an order. So we sort by the global name to get a stable order - // This ensures that assigned ids are also stable which is important since they are serialized. - let all_values = inventory::iter:: - .into_iter() - .map(|t| &**t.0) - .collect::>(); - Registry::new_from_items(all_values) -}); - -pub fn get_value_type_id(value: &'static ValueType) -> ValueTypeId { - VALUES.get_id(value) -} - -pub fn get_value_type(id: ValueTypeId) -> &'static ValueType { - VALUES.get_item(id) -} - -pub fn validate_value_type_id(id: ValueTypeId) -> Option { - VALUES.validate_id(id) -} - -static TRAITS: Lazy> = Lazy::new(|| { - // Inventory does not guarantee an order. So we sort by the global name to get a stable order - // This ensures that assigned ids are also stable. - let all_traits = inventory::iter:: - .into_iter() - .map(|t| &**t.0) - .collect::>(); - Registry::new_from_items(all_traits) -}); - -pub fn get_trait_type_id(trait_type: &'static TraitType) -> TraitTypeId { - TRAITS.get_id(trait_type) -} - -pub fn get_trait(id: TraitTypeId) -> &'static TraitType { - TRAITS.get_item(id) -} - -pub fn validate_trait_type_id(id: TraitTypeId) -> Option { - TRAITS.validate_id(id) -} diff --git a/turbopack/crates/turbo-tasks/src/registry/mod.rs b/turbopack/crates/turbo-tasks/src/registry/mod.rs new file mode 100644 index 00000000000000..03c379b4a29661 --- /dev/null +++ b/turbopack/crates/turbo-tasks/src/registry/mod.rs @@ -0,0 +1,246 @@ +use std::{cell::SyncUnsafeCell, num::NonZeroU16}; + +use anyhow::Error; +use once_cell::sync::Lazy; + +use crate::{ + TraitType, ValueType, + id::{FunctionId, TraitTypeId, ValueTypeId}, + native_function::NativeFunction, +}; + +mod registry_type; + +pub use registry_type::RegistryType; + +/// Declare a type as a compile-time-collected registry item. +/// +/// Generates pointer-based `Eq`, `PartialEq`, `Hash`, `Ord`, `PartialOrd` impls +/// and an `inventory::collect!` call for `&'static $ty`. +macro_rules! turbo_registry { + ($name:literal, $ty:ty) => { + inventory::collect!(&'static $ty); + + impl ::core::cmp::Eq for $ty {} + impl ::core::cmp::PartialEq for $ty { + fn eq(&self, other: &$ty) -> bool { + ::core::ptr::eq(self, other) + } + } + impl ::core::hash::Hash for $ty { + fn hash(&self, state: &mut H) { + ::core::ptr::hash(self, state) + } + } + impl ::core::cmp::Ord for $ty { + fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { + (self as *const Self).cmp(&(other as *const Self)) + } + } + impl ::core::cmp::PartialOrd for $ty { + fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { + Some(self.cmp(other)) + } + } + }; +} + +pub(crate) use turbo_registry; + +#[macro_export] +#[doc(hidden)] +macro_rules! turbo_register { + ($name:ident : $ty:ty = $value:expr) => { + static $name: $ty = $value; + $crate::macro_helpers::inventory_submit! { &$name } + }; + ($reg:ty => $name:ident : $ty:ty = $value:expr) => { + static $name: $ty = $value; + $crate::macro_helpers::inventory_submit! { &$name } + + impl $crate::macro_helpers::RegistryDef<$ty> for $reg { + const DEF: &'static $ty = &$name; + } + }; +} + +#[doc(hidden)] +pub trait RegistryDef { + const DEF: &'static T; +} + +/// A trait for types that can be registered in a registry. +/// +/// This allows the generic registry to work with different types +/// while maintaining their specific requirements. +trait Registerable: 'static + Eq + std::hash::Hash { + /// The ID type used for this registry item + type Id: Copy + From + std::ops::Deref + std::fmt::Display; + const TYPE_NAME: &'static str; + + /// Get the global registry type used for sorting and uniqueness validation + fn ty(&self) -> &RegistryType; +} + +impl Registerable for NativeFunction { + type Id = FunctionId; + const TYPE_NAME: &'static str = "Function"; + + fn ty(&self) -> &RegistryType { + &self.ty + } +} + +impl Registerable for ValueType { + type Id = ValueTypeId; + const TYPE_NAME: &'static str = "Value"; + fn ty(&self) -> &RegistryType { + &self.ty + } +} + +impl Registerable for TraitType { + type Id = TraitTypeId; + const TYPE_NAME: &'static str = "Trait"; + fn ty(&self) -> &RegistryType { + &self.ty + } +} + +/// Assign IDs to items and call post_init. Shared logic for all registry types. +fn init_registry(mut items: Vec<&'static T>) -> Box<[&'static T]> { + // Sort by global name for stable, deterministic ID assignment + items.sort_unstable_by_key(|item| item.ty().global_name); + + let mut id = NonZeroU16::MIN; + let mut prev_name: Option<&str> = None; + for item in items.iter() { + let global_name = item.ty().global_name; + if let Some(prev) = prev_name { + assert!( + prev != global_name, + "multiple {ty} items registered with name: {global_name}!", + ty = T::TYPE_NAME + ); + } + prev_name = Some(global_name); + // SAFETY: Single-threaded during Lazy init; no concurrent readers yet. + unsafe { std::ptr::write(SyncUnsafeCell::raw_get(&item.ty().id), u16::from(id)) }; + id = id.checked_add(1).expect("overflowing item ids"); + } + + items.into_boxed_slice() +} + +/// Get an item by its ID from a registry slice +#[inline] +fn get_item(registry: &Lazy>, id: T::Id) -> &'static T { + registry[*id as usize - 1] +} + +/// Get the ID for a registered item. Forces registry init if needed, which +/// assigns IDs to all items as a side effect. +#[inline] +fn get_id(registry: &Lazy>, item: &'static T) -> T::Id { + Lazy::force(registry); + // SAFETY: The ID write happens-before this read thanks to the fence inside of Lazy + let n = unsafe { std::ptr::read(item.ty().id.get()) }; + let Some(id) = NonZeroU16::new(n) else { + panic!( + "{ty} isn't registered: {item}", + ty = T::TYPE_NAME, + item = item.ty().global_name + ); + }; + T::Id::from(id) +} + +/// Validate that an ID is within the valid range +fn validate_id(registry: &Lazy>, id: T::Id) -> Option { + let len = registry.len(); + if *id as usize <= len { + None + } else { + Some(anyhow::anyhow!( + "Invalid {ty} id, {id} expected a value <= {len}", + ty = T::TYPE_NAME + )) + } +} + +static FUNCTIONS: Lazy> = Lazy::new(|| { + init_registry( + inventory::iter::<&'static NativeFunction> + .into_iter() + .copied() + .collect(), + ) +}); + +#[inline] +pub fn get_native_function(id: FunctionId) -> &'static NativeFunction { + get_item(&FUNCTIONS, id) +} + +#[inline] +pub fn get_function_id(func: &'static NativeFunction) -> FunctionId { + get_id(&FUNCTIONS, func) +} + +pub fn validate_function_id(id: FunctionId) -> Option { + validate_id(&FUNCTIONS, id) +} + +pub(crate) static VALUES: Lazy> = Lazy::new(|| { + let items = init_registry( + inventory::iter::<&'static ValueType> + .into_iter() + .copied() + .collect(), + ); + crate::value_type::register_all_trait_methods(&items); + items +}); + +#[inline] +pub fn get_value_type_id(value: &'static ValueType) -> ValueTypeId { + get_id(&VALUES, value) +} + +#[inline] +pub fn get_value_type(id: ValueTypeId) -> &'static ValueType { + get_item(&VALUES, id) +} + +pub fn validate_value_type_id(id: ValueTypeId) -> Option { + validate_id(&VALUES, id) +} + +/// Number of registered trait types. Forces TRAITS init. +#[inline] +pub(crate) fn trait_type_count() -> usize { + TRAITS.len() +} + +static TRAITS: Lazy> = Lazy::new(|| { + init_registry( + inventory::iter::<&'static TraitType> + .into_iter() + .copied() + .collect(), + ) +}); + +#[inline] +pub fn get_trait_type_id(trait_type: &'static TraitType) -> TraitTypeId { + get_id(&TRAITS, trait_type) +} + +#[inline] +pub fn get_trait(id: TraitTypeId) -> &'static TraitType { + get_item(&TRAITS, id) +} + +pub fn validate_trait_type_id(id: TraitTypeId) -> Option { + validate_id(&TRAITS, id) +} diff --git a/turbopack/crates/turbo-tasks/src/registry/registry_type.rs b/turbopack/crates/turbo-tasks/src/registry/registry_type.rs new file mode 100644 index 00000000000000..9bd196040678ad --- /dev/null +++ b/turbopack/crates/turbo-tasks/src/registry/registry_type.rs @@ -0,0 +1,36 @@ +use std::{any::TypeId, cell::SyncUnsafeCell, fmt::Debug}; + +pub struct RegistryType { + // The globally unique name for this function, used when persisting. + pub global_name: &'static str, + /// A readable name of the function that is used to reporting purposes. + pub name: &'static str, + /// The type's globally-unique TypeId. + pub type_id: TypeId, + /// Assigned during registry init (single-threaded inside Lazy). + pub(crate) id: SyncUnsafeCell, +} + +impl Eq for RegistryType {} +impl PartialEq for RegistryType { + fn eq(&self, other: &Self) -> bool { + self.type_id == other.type_id + } +} + +impl Debug for RegistryType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.global_name) + } +} + +impl RegistryType { + pub const fn new(name: &'static str, global_name: &'static str) -> Self { + Self { + name, + global_name, + type_id: TypeId::of::(), + id: SyncUnsafeCell::new(0), + } + } +} diff --git a/turbopack/crates/turbo-tasks/src/task/local_task.rs b/turbopack/crates/turbo-tasks/src/task/local_task.rs index 05d503a336f116..926c91f1e2b688 100644 --- a/turbopack/crates/turbo-tasks/src/task/local_task.rs +++ b/turbopack/crates/turbo-tasks/src/task/local_task.rs @@ -36,7 +36,7 @@ pub enum LocalTaskType { impl fmt::Display for LocalTaskType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - LocalTaskType::ResolveNative { native_fn } => write!(f, "*{}", native_fn.name), + LocalTaskType::ResolveNative { native_fn } => write!(f, "*{}", native_fn.ty.name), LocalTaskType::ResolveTrait { trait_method } => write!( f, "*{}::{}", diff --git a/turbopack/crates/turbo-tasks/src/task/shared_reference.rs b/turbopack/crates/turbo-tasks/src/task/shared_reference.rs index 0973989632efda..e7e93f783b0ac8 100644 --- a/turbopack/crates/turbo-tasks/src/task/shared_reference.rs +++ b/turbopack/crates/turbo-tasks/src/task/shared_reference.rs @@ -76,7 +76,7 @@ impl TurboBincodeEncode for TypedSharedReference { } else { Err(EncodeError::OtherString(format!( "{} is not encodable", - value_type.global_name + value_type.ty.global_name ))) } } @@ -92,7 +92,7 @@ impl TurboBincodeDecode for TypedSharedReference { } else { #[cold] fn not_decodable(value_type: &ValueType) -> DecodeError { - DecodeError::OtherString(format!("{} is not decodable", value_type.global_name)) + DecodeError::OtherString(format!("{} is not decodable", value_type.ty.global_name)) } Err(not_decodable(value_type)) } @@ -155,7 +155,7 @@ impl Display for TypedSharedReference { write!( f, "value of type {}", - registry::get_value_type(self.type_id).name + registry::get_value_type(self.type_id).ty.name ) } } diff --git a/turbopack/crates/turbo-tasks/src/task_statistics.rs b/turbopack/crates/turbo-tasks/src/task_statistics.rs index cc8656024ad168..0f5a6575cfd90f 100644 --- a/turbopack/crates/turbo-tasks/src/task_statistics.rs +++ b/turbopack/crates/turbo-tasks/src/task_statistics.rs @@ -73,7 +73,7 @@ impl Serialize for TaskStatistics { { let mut map = serializer.serialize_map(Some(self.inner.len()))?; for entry in &self.inner { - map.serialize_entry(entry.key().global_name, entry.value())?; + map.serialize_entry(entry.key().ty.global_name, entry.value())?; } map.end() } diff --git a/turbopack/crates/turbo-tasks/src/value_type.rs b/turbopack/crates/turbo-tasks/src/value_type.rs index 6f4b39be3a85b8..d96f81a3284a35 100644 --- a/turbopack/crates/turbo-tasks/src/value_type.rs +++ b/turbopack/crates/turbo-tasks/src/value_type.rs @@ -1,20 +1,26 @@ use std::{ + any::TypeId, + cell::SyncUnsafeCell, fmt::{self, Debug, Display, Formatter}, hash::Hash, }; -use auto_hash_map::{AutoMap, AutoSet}; use bincode::{Decode, Encode}; use tracing::Span; use turbo_bincode::{AnyDecodeFn, AnyEncodeFn}; use crate::{ - RawVc, SharedReference, TaskPriority, VcValueType, id::TraitTypeId, - macro_helpers::NativeFunction, magic_any::any_as_encode, registry, - task::shared_reference::TypedSharedReference, vc::VcCellMode, + RawVc, SharedReference, TaskPriority, VcValueType, + id::TraitTypeId, + macro_helpers::{CollectableTraitMethods, NativeFunction}, + magic_any::any_as_encode, + registry::{RegistryType, get_trait_type_id, trait_type_count, turbo_registry}, + task::shared_reference::TypedSharedReference, + vc::VcCellMode, }; type RawCellFactoryFn = fn(TypedSharedReference) -> RawVc; +type Vtable = &'static [&'static NativeFunction]; // TODO this type need some refactoring when multiple languages are added to // turbo-task In this case a trait_method might be of a different function type. @@ -26,14 +32,7 @@ type RawCellFactoryFn = fn(TypedSharedReference) -> RawVc; /// /// Contains a list of traits and trait methods that are available on that type. pub struct ValueType { - /// A readable name of the type - pub name: &'static str, - /// The fully qualitifed global name of the type. - pub global_name: &'static str, - /// Set of traits available - traits: AutoSet, - /// List of trait methods available - trait_methods: AutoMap<&'static TraitMethod, &'static NativeFunction>, + pub ty: RegistryType, /// Functions to convert to write the type to a buffer or read it from a buffer. pub bincode: Option<(AnyEncodeFn, AnyDecodeFn)>, @@ -48,43 +47,31 @@ pub struct ValueType { /// Because we allow resolving `Vc`, it's otherwise not possible /// for `RawVc` to know what the appropriate `VcCellMode` is. pub(crate) raw_cell: RawCellFactoryFn, -} -impl Hash for ValueType { - fn hash(&self, state: &mut H) { - (self as *const ValueType).hash(state); - } -} - -impl Eq for ValueType {} - -impl PartialEq for ValueType { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } + traits: SyncUnsafeCell, } impl Debug for ValueType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("ValueType"); - d.field("name", &self.name); - for trait_id in self.traits.iter() { - for (name, m) in ®istry::get_trait(*trait_id).methods { - if self.trait_methods.contains_key(&m) { - d.field(name, &"(trait fn)"); - } - } - } - d.finish() + f.debug_struct("ValueType") + .field("name", &self.ty.name) + .finish() } } impl Display for ValueType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.name) + f.write_str(self.ty.name) } } +struct ValueTypeTraits { + /// Flat array indexed by TraitTypeId (1-based, so index 0 = TraitTypeId 1). + /// `None` means this value type does not implement that trait. + /// The outer Option is None before init, Some after. + traits: Option]>>, +} + pub trait ManualEncodeWrapper: Encode { type Value; @@ -100,12 +87,12 @@ pub trait ManualDecodeWrapper: Decode<()> { impl ValueType { /// This is internally used by [`#[turbo_tasks::value]`][crate::value]. - pub fn new(global_name: &'static str) -> Self { + pub const fn new(global_name: &'static str) -> Self { Self::new_inner::(global_name, None) } /// This is internally used by [`#[turbo_tasks::value]`][crate::value]. - pub fn new_with_bincode>( + pub const fn new_with_bincode>( global_name: &'static str, ) -> Self { Self::new_inner::( @@ -130,7 +117,7 @@ impl ValueType { /// the wrapped type. /// /// [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules - pub fn new_with_bincode_wrappers< + pub const fn new_with_bincode_wrappers< T: VcValueType, E: ManualEncodeWrapper, D: ManualDecodeWrapper, @@ -153,56 +140,76 @@ impl ValueType { } // Helper for other constructor functions - fn new_inner( + const fn new_inner( global_name: &'static str, bincode: Option<(AnyEncodeFn, AnyDecodeFn)>, ) -> Self { Self { - name: std::any::type_name::(), - global_name, - traits: AutoSet::new(), - trait_methods: AutoMap::new(), + ty: RegistryType::new::(std::any::type_name::(), global_name), bincode, raw_cell: >::raw_cell, + traits: SyncUnsafeCell::new(ValueTypeTraits { traits: None }), } } - pub(crate) fn register_trait_method( - &mut self, - trait_method: &'static TraitMethod, - native_fn: &'static NativeFunction, - ) { - self.trait_methods.insert(trait_method, native_fn); + /// Returns the TypeId of the concrete type this ValueType represents. + pub fn type_id(&self) -> TypeId { + self.ty.type_id + } + + #[inline] + fn trait_info(&self) -> &ValueTypeTraits { + // SAFETY: Written during single-threaded Lazy init, read-only after. + unsafe { &*self.traits.get() } } + #[inline] pub fn get_trait_method( &self, trait_method: &'static TraitMethod, ) -> Option<&'static NativeFunction> { - match self.trait_methods.get(trait_method) { - Some(f) => Some(*f), - None => trait_method.default_method, - } + let trait_type_id = trait_method.trait_type_id(); + let vtable = self.trait_info().traits.as_ref()?[*trait_type_id as usize - 1]?; + Some(vtable[trait_method.index as usize]) } - pub(crate) fn register_trait(&mut self, trait_type: TraitTypeId) { - self.traits.insert(trait_type); + fn register_trait(&self, trait_type: &'static TraitType, trait_methods: Vtable) { + // SAFETY: Called only during single-threaded registry init + let traits = unsafe { &mut *self.traits.get() }; + let trait_type_id = get_trait_type_id(trait_type); + let array = traits + .traits + .get_or_insert_with(|| vec![None; trait_type_count()].into_boxed_slice()); + array[*trait_type_id as usize - 1] = Some(trait_methods); } + #[inline] pub fn has_trait(&self, trait_type: &TraitTypeId) -> bool { - self.traits.contains(trait_type) + self.trait_info() + .traits + .as_ref() + .is_some_and(|t| t[**trait_type as usize - 1].is_some()) } } -// A collectable struct for value types -pub struct CollectableValueType(pub &'static once_cell::sync::Lazy); +turbo_registry!("Value", ValueType); -inventory::collect! {CollectableValueType} +// Called during ValueType registry post_init to register all trait methods. +// Single-threaded during Lazy init. +pub(crate) fn register_all_trait_methods(_: &[&'static ValueType]) { + for entry in inventory::iter:: { + entry + .value_type + .register_trait(entry.trait_type, entry.methods) + } +} pub struct TraitMethod { - pub(crate) trait_name: &'static str, - pub(crate) method_name: &'static str, - pub(crate) default_method: Option<&'static NativeFunction>, + pub trait_type: &'static TraitType, + pub index: u8, + pub trait_name: &'static str, + pub method_name: &'static str, + pub default_method: Option<&'static NativeFunction>, } impl Hash for TraitMethod { fn hash(&self, state: &mut H) { @@ -227,6 +234,16 @@ impl Debug for TraitMethod { } } impl TraitMethod { + /// Returns the TraitTypeId by reading directly from the trait type's registry entry. + /// Must only be called after registry init. + #[inline] + fn trait_type_id(&self) -> TraitTypeId { + // SAFETY: Written during single-threaded Lazy init. Lazy provides acquire barrier. + let raw = unsafe { std::ptr::read(self.trait_type.ty.id.get()) }; + debug_assert!(raw != 0, "TraitMethod::trait_type_id not initialized"); + unsafe { TraitTypeId::new_unchecked(raw) } + } + pub(crate) fn resolve_span(&self, priority: TaskPriority) -> Span { tracing::trace_span!( "turbo_tasks::resolve_trait_call", @@ -236,58 +253,43 @@ impl TraitMethod { } } -#[derive(Debug)] pub struct TraitType { - pub name: &'static str, - pub global_name: &'static str, - pub(crate) methods: AutoMap<&'static str, TraitMethod>, + pub ty: RegistryType, + pub methods: phf::Map<&'static str, TraitMethod>, + pub method_names: &'static [&'static str], + pub default_methods: &'static [Option<&'static NativeFunction>], } -impl Hash for TraitType { - fn hash(&self, state: &mut H) { - (self as *const TraitType).hash(state); +impl Debug for TraitType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("TraitType"); + d.field("name", &self.ty.name); + for (name, method) in self.methods.entries() { + d.field(name, method); + } + d.finish() } } impl Display for TraitType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "trait {}", self.name) - } -} - -impl Eq for TraitType {} - -impl PartialEq for TraitType { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) + write!(f, "trait {}", self.ty.name) } } impl TraitType { - pub fn new( + pub const fn new( name: &'static str, global_name: &'static str, - trait_methods: Vec<(&'static str, Option<&'static NativeFunction>)>, + methods: phf::Map<&'static str, TraitMethod>, + method_names: &'static [&'static str], + default_methods: &'static [Option<&'static NativeFunction>], ) -> Self { - let mut methods = AutoMap::new(); - for (method_name, default_method) in trait_methods { - let prev = methods.insert( - method_name, - TraitMethod { - trait_name: name, - method_name, - default_method, - }, - ); - debug_assert!( - prev.is_none(), - "duplicate methods {method_name} registered on {global_name}" - ); - } Self { - name, - global_name, + ty: RegistryType::new::(name, global_name), methods, + method_names, + default_methods, } } @@ -296,6 +298,50 @@ impl TraitType { } } -pub struct CollectableTrait(pub &'static once_cell::sync::Lazy); +turbo_registry!("Trait", TraitType); -inventory::collect! {CollectableTrait} +pub trait TraitVtablePrototype { + const LEN: usize; + const NAMES: &'static [&'static str]; + const DEFAULTS: &'static [Option<&'static NativeFunction>]; +} + +pub(crate) const fn index_of_name(array: &'static [&'static str], name: &'static str) -> usize { + let mut i = 0; + 'outer: while i < array.len() { + if array[i].len() == name.len() { + let mut j = 0; + while j < name.len() { + if array[i].as_bytes()[j] != name.as_bytes()[j] { + i += 1; + continue 'outer; + } + j += 1; + } + return i; + } + i += 1; + } + panic!("Method not found!") +} + +pub const fn build_trait_vtable( + overrides: &[(&'static str, &'static NativeFunction)], +) -> [&'static NativeFunction; LEN] { + let mut methods = [&crate::native_function::VTABLE_DEFAULT; LEN]; + let mut i = 0; + while i < LEN { + if let Some(default) = B::DEFAULTS[i] { + methods[i] = default; + } + i += 1; + } + // N*M scan where N = overrides, M = method names. Both are small (single digits). + let mut i = 0; + while i < overrides.len() { + let (name, f) = overrides[i]; + methods[index_of_name(B::NAMES, name)] = f; + i += 1; + } + methods +} diff --git a/turbopack/crates/turbo-tasks/src/vc/mod.rs b/turbopack/crates/turbo-tasks/src/vc/mod.rs index 4cdddc021e410c..cae62c4fa7831b 100644 --- a/turbopack/crates/turbo-tasks/src/vc/mod.rs +++ b/turbopack/crates/turbo-tasks/src/vc/mod.rs @@ -278,7 +278,7 @@ where let raw_vc: RawVc = resolved.node; if let RawVc::TaskCell(task_id, CellId { type_id, index }) = raw_vc { let value_ty = registry::get_value_type(type_id); - Ok(format!("{}#{}: {}", value_ty.name, index, task_id)) + Ok(format!("{}#{}: {}", value_ty.ty.name, index, task_id)) } else { unreachable!() } diff --git a/turbopack/crates/turbo-tasks/src/vc/resolved.rs b/turbopack/crates/turbo-tasks/src/vc/resolved.rs index 26ccf8836224a8..0fbd0c77d77f84 100644 --- a/turbopack/crates/turbo-tasks/src/vc/resolved.rs +++ b/turbopack/crates/turbo-tasks/src/vc/resolved.rs @@ -277,7 +277,9 @@ where ::get_trait_type_id() != ::get_trait_type_id(), "Attempted to cast a type {} to itself, which is pointless. Use the value directly \ instead.", - crate::registry::get_trait(::get_trait_type_id()).global_name + crate::registry::get_trait(::get_trait_type_id()) + .ty + .global_name ); // `RawVc::TaskCell` already contains all the type information needed to check this // sidecast, so we don't need to read the underlying cell!