From 01d6a75d57c875978a00bd090b173333450da586 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Sat, 30 Aug 2025 23:33:50 -0700 Subject: [PATCH] try parallelizing awaited `$state` --- .../client/visitors/VariableDeclaration.js | 94 +++++++++++++++---- packages/svelte/src/internal/client/index.js | 1 + .../src/internal/client/reactivity/async.js | 12 +++ 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index 10a8c2f8fd..05e37ca4f3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -176,45 +176,83 @@ export function VariableDeclaration(node, context) { const value = /** @type {Expression} */ (args[0]) ?? b.void0; // TODO do we need the void 0? can we just omit it altogether? if (rune === '$state' || rune === '$state.raw') { + const state_declarators = []; + const current_chunk = context.state.current_parallelized_chunk; + const parallelize = + declarator.id.type === 'Identifier' && + context.state.analysis.instance?.scope === context.state.scope && + value.type === 'AwaitExpression' && + can_be_parallelized(value.argument, context.state.scope, context.state.analysis, [ + ...(current_chunk?.bindings ?? []), + ...bindings + ]); /** * @param {Identifier} id + * @param {Expression} visited * @param {Expression} value */ - const create_state_declarator = (id, value) => { + const create_state_declarator = (id, visited, value) => { const binding = /** @type {Binding} */ (context.state.scope.get(id.name)); const is_state = is_state_source(binding, context.state.analysis); - const is_proxy = should_proxy(value, context.state.scope); + const is_proxy = should_proxy(visited, context.state.scope); + const compose = []; + if (parallelize) { + if (rune === '$state' && is_proxy) { + compose.push(b.id('$.proxy')); - if (rune === '$state' && is_proxy) { - value = b.call('$.proxy', value); + if (dev && !is_state) { + compose.push( + b.arrow([b.id('proxy')], b.call('$.tag_proxy', b.id('proxy'), b.literal(id.name))) + ); + } + } - if (dev && !is_state) { - value = b.call('$.tag_proxy', value, b.literal(id.name)); + if (is_state) { + compose.push(b.id('$.state')); + if (dev) { + compose.push( + b.arrow([b.id('source')], b.call('$.tag', b.id('source'), b.literal(id.name))) + ); + } + } + } else { + if (rune === '$state' && is_proxy) { + value = b.call('$.proxy', value); + + if (dev && !is_state) { + value = b.call('$.tag_proxy', value, b.literal(id.name)); + } } - } - if (is_state) { - value = b.call('$.state', value); + if (is_state) { + value = b.call('$.state', value); - if (dev) { - value = b.call('$.tag', value, b.literal(id.name)); + if (dev) { + value = b.call('$.tag', value, b.literal(id.name)); + } } } - return value; + return parallelize && value.type === 'AwaitExpression' + ? b.call( + '$.async_compose', + /** @type {Expression} */ (context.visit(value.argument)), + ...compose + ) + : visited; }; if (declarator.id.type === 'Identifier') { const expression = /** @type {Expression} */ (context.visit(value)); - declarations.push( - b.declarator(declarator.id, create_state_declarator(declarator.id, expression)) + state_declarators.push( + b.declarator(declarator.id, create_state_declarator(declarator.id, expression, value)) ); } else { const tmp = b.id(context.state.scope.generate('tmp')); const { inserts, paths } = extract_paths(declarator.id, tmp); - declarations.push( + state_declarators.push( b.declarator(tmp, /** @type {Expression} */ (context.visit(value))), ...inserts.map(({ id, value }) => { id.name = context.state.scope.generate('$$array'); @@ -236,12 +274,36 @@ export function VariableDeclaration(node, context) { return b.declarator( path.node, binding?.kind === 'state' || binding?.kind === 'raw_state' - ? create_state_declarator(binding.node, value) + ? create_state_declarator(binding.node, value, path.expression) : value ); }) ); } + if (!parallelize) { + declarations.push(...state_declarators); + } else { + const declarators = state_declarators.map(({ id, init }) => ({ + id, + init: /** @type {Expression} */ (init) + })); + if (current_chunk && (current_chunk.kind === node.kind || current_chunk.kind === null)) { + current_chunk.declarators.push(...declarators); + current_chunk.bindings.push(...bindings); + current_chunk.position = position; + current_chunk.kind = node.kind; + } else { + /** @type {ParallelizedChunk} */ + const chunk = { + kind: node.kind, + declarators, + position, + bindings + }; + context.state.current_parallelized_chunk = chunk; + context.state.parallelized_chunks.push(chunk); + } + } continue; } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 04dd4cfb13..73628ef72f 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -101,6 +101,7 @@ export { export { all, async_body, + async_compose, for_await_track_reactivity_loss, save, track_reactivity_loss diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 181c33e9b9..0b404920b8 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -202,3 +202,15 @@ export function all(...promises) { ) ); } + +/** + * @param {Promise} promise + * @param {Array<(arg: any) => any>} fns + */ +export async function async_compose(promise, ...fns) { + let res = await promise; + for (const fn of fns) { + res = fn(res); + } + return res; +}