Skip to content

Commit 4e4f977

Browse files
lxsmnsycmizulubrenelz
authored
fix(1.x) - hash server function ids (#2143)
Co-authored-by: mizulu <202852165+mizulu@users.noreply.github.com> Co-authored-by: Brenley Dueck <brenleydueck@gmail.com>
1 parent 7052bde commit 4e4f977

2 files changed

Lines changed: 222 additions & 3 deletions

File tree

packages/start/config/index.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { config } from "vinxi/plugins/config";
99
import solid from "vite-plugin-solid";
1010
import { SolidStartClientFileRouter, SolidStartServerFileRouter } from "./fs-router.js";
1111
import { serverComponents } from "./server-components.js";
12+
import xxHash32 from "./xxhash32.js";
1213

1314
const DEFAULT_EXTENSIONS = ["js", "jsx", "ts", "tsx"];
1415

@@ -37,6 +38,10 @@ function solidStartServerFsRouter(config) {
3738
);
3839
}
3940

41+
function getFunctionId(id) {
42+
return xxHash32(id).toString(16);
43+
}
44+
4045
const SolidStartServerFnsPlugin = createTanStackServerFnPlugin({
4146
// This is the ID that will be available to look up and import
4247
// our server function manifest and resolve its module
@@ -47,23 +52,23 @@ const SolidStartServerFnsPlugin = createTanStackServerFnPlugin({
4752
fileURLToPath(new URL("../dist/runtime/server-runtime.js", import.meta.url))
4853
)}"`,
4954
replacer: opts =>
50-
`createServerReference(${() => {}}, '${opts.functionId}', '${opts.extractedFilename}')`
55+
`createServerReference(${() => {}}, '${(opts.functionId)}', '${getFunctionId(opts.extractedFilename)}')`
5156
},
5257
ssr: {
5358
getRuntimeCode: () =>
5459
`import { createServerReference } from '${normalize(
5560
fileURLToPath(new URL("../dist/runtime/server-fns-runtime.js", import.meta.url))
5661
)}'`,
5762
replacer: opts =>
58-
`createServerReference(${opts.fn}, '${opts.functionId}', '${opts.extractedFilename}')`
63+
`createServerReference(${opts.fn}, '${(opts.functionId)}', '${getFunctionId(opts.extractedFilename)}')`
5964
},
6065
server: {
6166
getRuntimeCode: () =>
6267
`import { createServerReference } from '${normalize(
6368
fileURLToPath(new URL("../dist/runtime/server-fns-runtime.js", import.meta.url))
6469
)}'`,
6570
replacer: opts =>
66-
`createServerReference(${opts.fn}, '${opts.functionId}', '${opts.extractedFilename}')`
71+
`createServerReference(${opts.fn}, '${(opts.functionId)}', '${getFunctionId(opts.extractedFilename)}')`
6772
}
6873
});
6974

packages/start/config/xxhash32.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// @ts-nocheck
2+
/**
3+
* Copyright (c) 2019 Jason Dent
4+
* https://github.com/Jason3S/xxhash
5+
*/
6+
const PRIME32_1 = 2654435761;
7+
const PRIME32_2 = 2246822519;
8+
const PRIME32_3 = 3266489917;
9+
const PRIME32_4 = 668265263;
10+
const PRIME32_5 = 374761393;
11+
12+
function toUtf8(text) {
13+
const bytes = [];
14+
for (let i = 0, n = text.length; i < n; ++i) {
15+
const c = text.charCodeAt(i);
16+
if (c < 0x80) {
17+
bytes.push(c);
18+
} else if (c < 0x800) {
19+
bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
20+
} else if (c < 0xd800 || c >= 0xe000) {
21+
bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
22+
} else {
23+
const cp =
24+
0x10000 + (((c & 0x3ff) << 10) | (text.charCodeAt(++i) & 0x3ff));
25+
bytes.push(
26+
0xf0 | ((cp >> 18) & 0x7),
27+
0x80 | ((cp >> 12) & 0x3f),
28+
0x80 | ((cp >> 6) & 0x3f),
29+
0x80 | (cp & 0x3f),
30+
);
31+
}
32+
}
33+
return new Uint8Array(bytes);
34+
}
35+
/**
36+
*
37+
* @param buffer - byte array or string
38+
* @param seed - optional seed (32-bit unsigned);
39+
*/
40+
export default function xxHash32(
41+
buffer,
42+
seed = 0,
43+
) {
44+
buffer = typeof buffer === 'string' ? toUtf8(buffer) : buffer;
45+
const b = buffer;
46+
47+
/*
48+
Step 1. Initialize internal accumulators
49+
Each accumulator gets an initial value based on optional seed input.
50+
Since the seed is optional, it can be 0.
51+
```
52+
u32 acc1 = seed + PRIME32_1 + PRIME32_2;
53+
u32 acc2 = seed + PRIME32_2;
54+
u32 acc3 = seed + 0;
55+
u32 acc4 = seed - PRIME32_1;
56+
```
57+
Special case : input is less than 16 bytes
58+
When input is too small (< 16 bytes), the algorithm will not process any stripe.
59+
Consequently, it will not make use of parallel accumulators.
60+
In which case, a simplified initialization is performed, using a single accumulator :
61+
u32 acc = seed + PRIME32_5;
62+
The algorithm then proceeds directly to step 4.
63+
*/
64+
65+
let acc = (seed + PRIME32_5) & 0xffffffff;
66+
let offset = 0;
67+
68+
if (b.length >= 16) {
69+
const accN = [
70+
(seed + PRIME32_1 + PRIME32_2) & 0xffffffff,
71+
(seed + PRIME32_2) & 0xffffffff,
72+
(seed + 0) & 0xffffffff,
73+
(seed - PRIME32_1) & 0xffffffff,
74+
];
75+
76+
/*
77+
Step 2. Process stripes
78+
A stripe is a contiguous segment of 16 bytes. It is evenly divided into 4 lanes,
79+
of 4 bytes each. The first lane is used to update accumulator 1, the second lane
80+
is used to update accumulator 2, and so on. Each lane read its associated 32-bit
81+
value using little-endian convention. For each {lane, accumulator}, the update
82+
process is called a round, and applies the following formula :
83+
```
84+
accN = accN + (laneN * PRIME32_2);
85+
accN = accN <<< 13;
86+
accN = accN * PRIME32_1;
87+
```
88+
This shuffles the bits so that any bit from input lane impacts several bits in
89+
output accumulator. All operations are performed modulo 2^32.
90+
Input is consumed one full stripe at a time. Step 2 is looped as many times as
91+
necessary to consume the whole input, except the last remaining bytes which cannot
92+
form a stripe (< 16 bytes). When that happens, move to step 3.
93+
*/
94+
95+
const b = buffer;
96+
const limit = b.length - 16;
97+
let lane = 0;
98+
for (offset = 0; (offset & 0xfffffff0) <= limit; offset += 4) {
99+
const i = offset;
100+
const laneN0 = b[i + 0] + (b[i + 1] << 8);
101+
const laneN1 = b[i + 2] + (b[i + 3] << 8);
102+
const laneNP = laneN0 * PRIME32_2 + ((laneN1 * PRIME32_2) << 16);
103+
let acc = (accN[lane] + laneNP) & 0xffffffff;
104+
acc = (acc << 13) | (acc >>> 19);
105+
const acc0 = acc & 0xffff;
106+
const acc1 = acc >>> 16;
107+
accN[lane] = (acc0 * PRIME32_1 + ((acc1 * PRIME32_1) << 16)) & 0xffffffff;
108+
lane = (lane + 1) & 0x3;
109+
}
110+
111+
/*
112+
Step 3. Accumulator convergence
113+
All 4 lane accumulators from previous steps are merged to produce a
114+
single remaining accumulator
115+
of same width (32-bit). The associated formula is as follows :
116+
```
117+
acc = (acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18);
118+
```
119+
*/
120+
acc =
121+
(((accN[0] << 1) | (accN[0] >>> 31)) +
122+
((accN[1] << 7) | (accN[1] >>> 25)) +
123+
((accN[2] << 12) | (accN[2] >>> 20)) +
124+
((accN[3] << 18) | (accN[3] >>> 14))) &
125+
0xffffffff;
126+
}
127+
128+
/*
129+
Step 4. Add input length
130+
The input total length is presumed known at this stage.
131+
This step is just about adding the length to
132+
accumulator, so that it participates to final mixing.
133+
```
134+
acc = acc + (u32)inputLength;
135+
```
136+
*/
137+
acc = (acc + buffer.length) & 0xffffffff;
138+
139+
/*
140+
Step 5. Consume remaining input
141+
There may be up to 15 bytes remaining to consume from the input.
142+
The final stage will digest them according
143+
to following pseudo-code :
144+
```
145+
while (remainingLength >= 4) {
146+
lane = read_32bit_little_endian(input_ptr);
147+
acc = acc + lane * PRIME32_3;
148+
acc = (acc <<< 17) * PRIME32_4;
149+
input_ptr += 4; remainingLength -= 4;
150+
}
151+
```
152+
This process ensures that all input bytes are present in the final mix.
153+
*/
154+
155+
const limit = buffer.length - 4;
156+
for (; offset <= limit; offset += 4) {
157+
const i = offset;
158+
const laneN0 = b[i + 0] + (b[i + 1] << 8);
159+
const laneN1 = b[i + 2] + (b[i + 3] << 8);
160+
const laneP = laneN0 * PRIME32_3 + ((laneN1 * PRIME32_3) << 16);
161+
acc = (acc + laneP) & 0xffffffff;
162+
acc = (acc << 17) | (acc >>> 15);
163+
acc =
164+
((acc & 0xffff) * PRIME32_4 + (((acc >>> 16) * PRIME32_4) << 16)) &
165+
0xffffffff;
166+
}
167+
168+
/*
169+
```
170+
while (remainingLength >= 1) {
171+
lane = read_byte(input_ptr);
172+
acc = acc + lane * PRIME32_5;
173+
acc = (acc <<< 11) * PRIME32_1;
174+
input_ptr += 1; remainingLength -= 1;
175+
}
176+
```
177+
*/
178+
179+
for (; offset < b.length; ++offset) {
180+
const lane = b[offset];
181+
acc += lane * PRIME32_5;
182+
acc = (acc << 11) | (acc >>> 21);
183+
acc =
184+
((acc & 0xffff) * PRIME32_1 + (((acc >>> 16) * PRIME32_1) << 16)) &
185+
0xffffffff;
186+
}
187+
188+
/*
189+
Step 6. Final mix (avalanche)
190+
The final mix ensures that all input bits have a chance to impact any bit in
191+
the output digest, resulting in an unbiased distribution. This is also called
192+
avalanche effect.
193+
```
194+
acc = acc xor (acc >> 15);
195+
acc = acc * PRIME32_2;
196+
acc = acc xor (acc >> 13);
197+
acc = acc * PRIME32_3;
198+
acc = acc xor (acc >> 16);
199+
```
200+
*/
201+
202+
acc ^= acc >>> 15;
203+
acc =
204+
(((acc & 0xffff) * PRIME32_2) & 0xffffffff) +
205+
(((acc >>> 16) * PRIME32_2) << 16);
206+
acc ^= acc >>> 13;
207+
acc =
208+
(((acc & 0xffff) * PRIME32_3) & 0xffffffff) +
209+
(((acc >>> 16) * PRIME32_3) << 16);
210+
acc ^= acc >>> 16;
211+
212+
// turn any negatives back into a positive number;
213+
return acc < 0 ? acc + 4294967296 : acc;
214+
}

0 commit comments

Comments
 (0)