LibJS: Use simdjson for JSON.parse

Replace the custom AK JSON parser with simdjson for parsing JSON in
LibJS. This eliminates the intermediate AK::JsonValue object graph,
going directly from JSON text to JS::Value.

simdjson's on-demand API parses at ~4GB/s and only materializes values
as they are accessed, making this both faster and more memory efficient
than the previous approach.

The AK JSON parser is still used elsewhere (WebDriver protocol, config
files, etc.) but LibJS now uses simdjson exclusively for JSON.parse()
and JSON.rawJSON().
This commit is contained in:
Andreas Kling
2026-01-11 21:09:41 +01:00
committed by Tim Flynn
parent 097aaef648
commit 5e0ee26e8b
Notes: github-actions[bot] 2026-01-12 18:54:37 +00:00
8 changed files with 497 additions and 59 deletions

View File

@@ -74,3 +74,73 @@ test("does not truncate large integers", () => {
expect(JSON.parse("18446744073709551616")).toEqual(18446744073709551616);
expect(JSON.parse("18446744073709551617")).toEqual(18446744073709551617);
});
test("number overflow to infinity", () => {
expect(JSON.parse("1e309")).toBe(Infinity);
expect(JSON.parse("-1e309")).toBe(-Infinity);
expect(JSON.parse("1e-400")).toBe(0);
});
test("rejects invalid number formats", () => {
// Leading zeros not allowed
expect(() => JSON.parse("01")).toThrow(SyntaxError);
expect(() => JSON.parse("-01")).toThrow(SyntaxError);
expect(() => JSON.parse("00")).toThrow(SyntaxError);
expect(() => JSON.parse("007")).toThrow(SyntaxError);
// Trailing decimal point not allowed
expect(() => JSON.parse("1.")).toThrow(SyntaxError);
expect(() => JSON.parse("0.")).toThrow(SyntaxError);
expect(() => JSON.parse("-1.")).toThrow(SyntaxError);
// Other invalid formats
expect(() => JSON.parse("+1")).toThrow(SyntaxError);
expect(() => JSON.parse(".1")).toThrow(SyntaxError);
expect(() => JSON.parse("1e")).toThrow(SyntaxError);
expect(() => JSON.parse("1e+")).toThrow(SyntaxError);
expect(() => JSON.parse("1e-")).toThrow(SyntaxError);
});
test("rejects trailing content", () => {
expect(() => JSON.parse("123 garbage")).toThrow(SyntaxError);
expect(() => JSON.parse("null garbage")).toThrow(SyntaxError);
expect(() => JSON.parse("true garbage")).toThrow(SyntaxError);
expect(() => JSON.parse('"string" garbage')).toThrow(SyntaxError);
expect(() => JSON.parse("[] garbage")).toThrow(SyntaxError);
expect(() => JSON.parse("{} garbage")).toThrow(SyntaxError);
});
test("string escape sequences", () => {
expect(JSON.parse('"\\""')).toBe('"');
expect(JSON.parse('"\\\\"')).toBe("\\");
expect(JSON.parse('"\\/"')).toBe("/");
expect(JSON.parse('"\\b"')).toBe("\b");
expect(JSON.parse('"\\f"')).toBe("\f");
expect(JSON.parse('"\\n"')).toBe("\n");
expect(JSON.parse('"\\r"')).toBe("\r");
expect(JSON.parse('"\\t"')).toBe("\t");
expect(JSON.parse('"\\u0041"')).toBe("A");
expect(JSON.parse('"\\u0000"')).toBe("\0");
});
test("unicode and surrogate pairs", () => {
expect(JSON.parse('"café"')).toBe("café");
expect(JSON.parse('"日本語"')).toBe("日本語");
expect(JSON.parse('"\\uD83D\\uDE00"')).toBe("😀");
expect(JSON.parse('"\\u4e2d\\u6587"')).toBe("中文");
// Lone surrogates (valid JSON)
expect(JSON.parse('"\\uD800"')).toBe("\uD800");
expect(JSON.parse('"\\uDFFF"')).toBe("\uDFFF");
});
test("whitespace handling", () => {
expect(JSON.parse(" null")).toBe(null);
expect(JSON.parse("null ")).toBe(null);
expect(JSON.parse(" null ")).toBe(null);
expect(JSON.parse("\t123")).toBe(123);
expect(JSON.parse("123\n")).toBe(123);
expect(JSON.parse("\r\n123\r\n")).toBe(123);
expect(JSON.parse(" { } ")).toEqual({});
expect(JSON.parse(" [ ] ")).toEqual([]);
});