diff --git a/CMakeLists.txt b/CMakeLists.txt index 54ae1b1a..500b6a89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,18 @@ set(LLHTTP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/api.c ) +# On x86-64, compile a second instantiation of the parser with SSE4.2 enabled so +# llhttp_execute() can select it at runtime (see src/api.c). The default build +# passes no -march, so the SSE4.2 span scanners in the generated code are +# otherwise never compiled in. Skipped on MSVC (no __builtin_cpu_supports). +if(NOT MSVC AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") + list(APPEND LLHTTP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/llhttp_sse42.c) + set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/src/llhttp_sse42.c + PROPERTIES COMPILE_OPTIONS "-msse4.2" + ) +endif() + set(LLHTTP_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/llhttp.h ) diff --git a/Makefile b/Makefile index 16fe819f..f92fbe65 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,15 @@ endif INCLUDES += -Ibuild/ +# On x86-64, also build a second instantiation of the generated parser with +# -msse4.2 so llhttp_execute() can select the SSE4.2 span scanners at runtime +# (see src/native/api.c). The default build passes no -march, so without this +# the SSE4.2 code generated under `#ifdef __SSE4_2__` is never compiled in. +ARCH ?= $(shell uname -m) +ifneq (,$(filter $(ARCH),x86_64 amd64 AMD64)) + SSE42_OBJ = build/c/llhttp_sse42.o +endif + INSTALL ?= install PREFIX ?= /usr/local LIBDIR = $(PREFIX)/lib @@ -23,17 +32,20 @@ clean: rm -rf build/ rm -rf test/tmp -build/libllhttp.so: build/c/llhttp.o build/native/api.o \ +build/libllhttp.so: build/c/llhttp.o $(SSE42_OBJ) build/native/api.o \ build/native/http.o $(CLANG) -shared $^ -o $@ -build/libllhttp.a: build/c/llhttp.o build/native/api.o \ +build/libllhttp.a: build/c/llhttp.o $(SSE42_OBJ) build/native/api.o \ build/native/http.o - $(AR) rcs $@ build/c/llhttp.o build/native/api.o build/native/http.o + $(AR) rcs $@ build/c/llhttp.o $(SSE42_OBJ) build/native/api.o build/native/http.o build/c/llhttp.o: build/c/llhttp.c $(CLANG) $(CFLAGS) $(INCLUDES) -c $< -o $@ +build/c/llhttp_sse42.o: build/c/llhttp_sse42.c build/c/llhttp.c + $(CLANG) $(CFLAGS) -msse4.2 $(INCLUDES) -c $< -o $@ + build/native/%.o: src/native/%.c build/llhttp.h src/native/api.h \ build/native $(CLANG) $(CFLAGS) $(INCLUDES) -c $< -o $@ @@ -52,6 +64,8 @@ release: clean all generate mkdir -p release/include cp -rf build/llhttp.h release/include/ cp -rf build/c/llhttp.c release/src/ + cp -rf build/c/llhttp_sse42.c release/src/ + cp -rf build/c/llhttp_sse42_rename.h release/src/ cp -rf src/native/*.c release/src/ cp -rf src/llhttp.gyp release/ cp -rf src/common.gypi release/ diff --git a/bin/generate.ts b/bin/generate.ts old mode 100755 new mode 100644 index 727269dd..dbb4c993 --- a/bin/generate.ts +++ b/bin/generate.ts @@ -11,6 +11,8 @@ const semverRE = /^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\ const C_FILE = resolve(__dirname, '../build/c/llhttp.c'); const HEADER_FILE = resolve(__dirname, '../build/llhttp.h'); +const SSE42_RENAME_FILE = resolve(__dirname, '../build/c/llhttp_sse42_rename.h'); +const SSE42_C_FILE = resolve(__dirname, '../build/c/llhttp_sse42.c'); const pkg = JSON.parse( readFileSync(resolve(__dirname, '..', 'package.json')).toString(), @@ -49,3 +51,32 @@ ${nativeHeaders} mkdirSync(dirname(C_FILE), { recursive: true }); writeFileSync(HEADER_FILE, headers); writeFileSync(C_FILE, generated.c); + +// A second instantiation of the generated parser, compiled with -msse4.2, lets +// llhttp_execute() use the SSE4.2 span scanners at runtime on default x86-64 +// builds (which pass no -march, so the `#ifdef __SSE4_2__` code is otherwise +// dead). The parser defines symbols prefixed `llhttp__internal`; the callbacks +// it imports are prefixed `llhttp__on`/`llhttp__before`/`llhttp__after` and stay +// shared. Rename only the defined `llhttp__internal*` symbols so the second copy +// links beside the first. Derived from the generated source, so it stays correct +// across grammar changes. +const internalSymbols = Array.from( + new Set(generated.c.match(/\bllhttp__internal[0-9A-Za-z_]*/g) ?? []), +).sort(); +const renameHeader = `/* Generated by bin/generate.ts. Renames the defined symbols of llhttp.c so a + * second, -msse4.2 instantiation (llhttp_sse42.c) can link beside the first. */ +#ifndef INCLUDE_LLHTTP_SSE42_RENAME_H_ +#define INCLUDE_LLHTTP_SSE42_RENAME_H_ +${internalSymbols.map((s) => `#define ${s} ${s}_sse42`).join('\n')} +#endif /* INCLUDE_LLHTTP_SSE42_RENAME_H_ */ +`; +const sse42Wrapper = `/* Generated by bin/generate.ts. See src/native/api.c: a second instantiation of + * the generated parser with the SSE4.2 span scanners enabled, selected once at + * runtime by llhttp_execute(). Built with -msse4.2 by the build system. */ +#if defined(__x86_64__) || defined(_M_X64) +#include "llhttp_sse42_rename.h" +#include "llhttp.c" +#endif +`; +writeFileSync(SSE42_RENAME_FILE, renameHeader); +writeFileSync(SSE42_C_FILE, sse42Wrapper); diff --git a/src/native/api.c b/src/native/api.c index ae5e862d..3c483659 100644 --- a/src/native/api.c +++ b/src/native/api.c @@ -122,9 +122,33 @@ void llhttp_reset(llhttp_t* parser) { } +#if (defined(__x86_64__) || defined(_M_X64)) && !defined(__SSE4_2__) && \ + (defined(__GNUC__) || defined(__clang__)) +/* A second instantiation of the generated parser, compiled with -msse4.2 (see + * llhttp_sse42.c), which turns on the SSE4.2 span scanners that the generated + * code guards with `#ifdef __SSE4_2__`. The default x86-64 build (no -march) + * never contains them, so pick the SSE4.2 build once at runtime. Both + * instantiations come from the same generated source, so the parser state + * layout is identical and a parser may be driven by either. */ +llhttp_errno_t llhttp__internal_execute_sse42(llhttp_t* state, const char* p, + const char* endp); + +static int llhttp__sse42 = -1; + +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { + if (llhttp__sse42 < 0) { + llhttp__sse42 = __builtin_cpu_supports("sse4.2") ? 1 : 0; + } + if (llhttp__sse42) { + return llhttp__internal_execute_sse42(parser, data, data + len); + } + return llhttp__internal_execute(parser, data, data + len); +} +#else llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { return llhttp__internal_execute(parser, data, data + len); } +#endif void llhttp_settings_init(llhttp_settings_t* settings) {