diff --git a/src/HttpResponseWrapper.h b/src/HttpResponseWrapper.h index ec71920c..f98c86d3 100644 --- a/src/HttpResponseWrapper.h +++ b/src/HttpResponseWrapper.h @@ -19,6 +19,7 @@ #include "Utilities.h" #include +#include "v8-fast-api-calls.h" using namespace v8; thread_local int insideCorkCallback = 0; @@ -565,6 +566,46 @@ struct HttpResponseWrapper { } } + /* V8 fast call fast paths - called directly from JIT-optimised code. + * Requirements: no JS heap allocation, no JS execution, POD or ArrayBuffer args only. + * A null internal-field pointer (closed/invalid response) sets options.fallback = true so V8 + * re-invokes the slow path which throws the proper exception. */ + + template + static uint32_t res_getRemotePort_fast(v8::Local receiver, v8::FastApiCallbackOptions& options) { + void *ptr = receiver->GetAlignedPointerFromInternalField(0); + if (!ptr) { options.fallback = true; return 0; } + return ((uWS::HttpResponse *) ptr)->getRemotePort(); + } + + template + static uint32_t res_getProxiedRemotePort_fast(v8::Local receiver, v8::FastApiCallbackOptions& options) { + void *ptr = receiver->GetAlignedPointerFromInternalField(0); + if (!ptr) { options.fallback = true; return 0; } + return ((uWS::HttpResponse *) ptr)->getProxiedRemotePort(); + } + + template + static double res_getWriteOffset_fast(v8::Local receiver, v8::FastApiCallbackOptions& options) { + void *ptr = receiver->GetAlignedPointerFromInternalField(0); + if (!ptr) { options.fallback = true; return 0.0; } + return (double) ((uWS::HttpResponse *) ptr)->getWriteOffset(); + } + + /* write() returns bool (true = success, false = backpressure). Handles TCP, TLS, and QUIC. */ + template + static bool res_write_fast(v8::Local receiver, const v8::FastApiTypedArray& data, v8::FastApiCallbackOptions& options) { + void *ptr = receiver->GetAlignedPointerFromInternalField(0); + if (!ptr) { options.fallback = true; return false; } + uint8_t *buf = nullptr; + if (!data.getStorageIfAligned(&buf)) { options.fallback = true; return false; } + if constexpr (PROTOCOL == 2) { + return ((uWS::Http3Response *) ptr)->write(std::string_view((char *) buf, data.length())); + } else { + return ((uWS::HttpResponse *) ptr)->write(std::string_view((char *) buf, data.length())); + } + } + /* 0 = TCP, 1 = TLS, 2 = QUIC, 3 = CACHE */ template static Local init(Isolate *isolate) { @@ -580,7 +621,9 @@ struct HttpResponseWrapper { } resTemplateLocal->InstanceTemplate()->SetInternalFieldCount(1); - /* Register our functions */ + /* Register our functions. + * Static CFunction descriptors are per template specialisation (SSL 0/1/2/3) + * and are initialised once because the addon is a single translation unit. */ resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "end", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_end)); /* Cache has almost nothing wrapped yet */ @@ -588,7 +631,8 @@ struct HttpResponseWrapper { resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "writeStatus", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_writeStatus)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "endWithoutBody", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_endWithoutBody)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "tryEnd", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_tryEnd)); - resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "write", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_write)); + static v8::CFunction fast_write = v8::CFunction::Make(res_write_fast); + resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "write", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_write, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, &fast_write)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "writeHeader", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_writeHeader)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "close", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_close)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "onWritable", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_onWritable)); @@ -598,17 +642,20 @@ struct HttpResponseWrapper { /* QUIC has a lot of functions unimplemented */ if constexpr (SSL != 2) { resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "onFullData", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_onFullData)); - resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getWriteOffset", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getWriteOffset)); + static v8::CFunction fast_getWriteOffset = v8::CFunction::Make(res_getWriteOffset_fast); + resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getWriteOffset", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getWriteOffset, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasNoSideEffect, &fast_getWriteOffset)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "maxRemainingBodyLength", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_maxRemainingBodyLength)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemoteAddress", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getRemoteAddress)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "cork", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_cork)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "collect", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_cork)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "upgrade", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_upgrade)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemoteAddressAsText", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getRemoteAddressAsText)); - resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemotePort", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getRemotePort)); + static v8::CFunction fast_getRemotePort = v8::CFunction::Make(res_getRemotePort_fast); + resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemotePort", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getRemotePort, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasNoSideEffect, &fast_getRemotePort)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getProxiedRemoteAddress", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getProxiedRemoteAddress)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getProxiedRemoteAddressAsText", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getProxiedRemoteAddressAsText)); - resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getProxiedRemotePort", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getProxiedRemotePort)); + static v8::CFunction fast_getProxiedRemotePort = v8::CFunction::Make(res_getProxiedRemotePort_fast); + resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getProxiedRemotePort", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getProxiedRemotePort, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasNoSideEffect, &fast_getProxiedRemotePort)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "pause", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_pause)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "resume", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_resume)); } diff --git a/src/WebSocketWrapper.h b/src/WebSocketWrapper.h index 4b02768e..c32c322e 100644 --- a/src/WebSocketWrapper.h +++ b/src/WebSocketWrapper.h @@ -19,6 +19,7 @@ #include "Utilities.h" #include +#include "v8-fast-api-calls.h" using namespace v8; /* todo: probably isCorked, cork should be exposed? */ @@ -319,6 +320,73 @@ struct WebSocketWrapper { } } + /* V8 fast call fast paths - called directly from JIT-optimised code. + * Requirements: no JS heap allocation, no JS execution, POD or ArrayBuffer args only. + * A null internal-field pointer (closed socket) sets options.fallback = true so V8 + * re-invokes the slow path which throws the proper exception. */ + + template + static uint32_t uWS_WebSocket_getBufferedAmount_fast(v8::Local receiver, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + return ws->getBufferedAmount(); + } + + template + static uint32_t uWS_WebSocket_getRemotePort_fast(v8::Local receiver, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + return ws->getRemotePort(); + } + + /* The three send-fragment helpers share the same ArrayBuffer + bool(s) pattern. */ + template + static uint32_t uWS_WebSocket_sendFirstFragment_fast(v8::Local receiver, const v8::FastApiTypedArray& message, bool isBinary, bool compress, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + uint8_t *data = nullptr; + if (!message.getStorageIfAligned(&data)) { options.fallback = true; return 0; } + return ws->sendFirstFragment(std::string_view((char *) data, message.length()), + isBinary ? uWS::OpCode::BINARY : uWS::OpCode::TEXT, compress); + } + + template + static uint32_t uWS_WebSocket_sendFragment_fast(v8::Local receiver, const v8::FastApiTypedArray& message, bool compress, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + uint8_t *data = nullptr; + if (!message.getStorageIfAligned(&data)) { options.fallback = true; return 0; } + return ws->sendFragment(std::string_view((char *) data, message.length()), compress); + } + + template + static uint32_t uWS_WebSocket_sendLastFragment_fast(v8::Local receiver, const v8::FastApiTypedArray& message, bool compress, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + uint8_t *data = nullptr; + if (!message.getStorageIfAligned(&data)) { options.fallback = true; return 0; } + return ws->sendLastFragment(std::string_view((char *) data, message.length()), compress); + } + + template + static uint32_t uWS_WebSocket_send_fast(v8::Local receiver, const v8::FastApiTypedArray& message, bool isBinary, bool compress, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + uint8_t *data = nullptr; + if (!message.getStorageIfAligned(&data)) { options.fallback = true; return 0; } + return ws->send(std::string_view((char *) data, message.length()), + isBinary ? uWS::OpCode::BINARY : uWS::OpCode::TEXT, compress); + } + + template + static uint32_t uWS_WebSocket_ping_fast(v8::Local receiver, const v8::FastApiTypedArray& message, v8::FastApiCallbackOptions& options) { + auto *ws = (uWS::WebSocket *) receiver->GetAlignedPointerFromInternalField(0); + if (!ws) { options.fallback = true; return 0; } + uint8_t *data = nullptr; + if (!message.getStorageIfAligned(&data)) { options.fallback = true; return 0; } + return ws->send(std::string_view((char *) data, message.length()), uWS::OpCode::PING); + } + template static Local init(Isolate *isolate) { Local wsTemplateLocal = FunctionTemplate::New(isolate); @@ -329,24 +397,33 @@ struct WebSocketWrapper { } wsTemplateLocal->InstanceTemplate()->SetInternalFieldCount(1); - /* Register our functions */ - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "sendFirstFragment", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_sendFirstFragment)); - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "sendFragment", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_sendFragment)); - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "sendLastFragment", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_sendLastFragment)); + /* Register our functions. + * Static CFunction descriptors are per template specialisation (SSL=true vs SSL=false) + * and are initialised once because the addon is a single translation unit. */ + static v8::CFunction fast_sendFirstFragment = v8::CFunction::Make(uWS_WebSocket_sendFirstFragment_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "sendFirstFragment", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_sendFirstFragment, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, &fast_sendFirstFragment)); + static v8::CFunction fast_sendFragment = v8::CFunction::Make(uWS_WebSocket_sendFragment_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "sendFragment", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_sendFragment, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, &fast_sendFragment)); + static v8::CFunction fast_sendLastFragment = v8::CFunction::Make(uWS_WebSocket_sendLastFragment_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "sendLastFragment", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_sendLastFragment, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, &fast_sendLastFragment)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getUserData", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getUserData)); - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "send", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_send)); + static v8::CFunction fast_send = v8::CFunction::Make(uWS_WebSocket_send_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "send", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_send, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, &fast_send)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "end", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_end)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "close", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_close)); - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getBufferedAmount", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getBufferedAmount)); + static v8::CFunction fast_getBufferedAmount = v8::CFunction::Make(uWS_WebSocket_getBufferedAmount_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getBufferedAmount", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getBufferedAmount, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasNoSideEffect, &fast_getBufferedAmount)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemoteAddress", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getRemoteAddress)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "subscribe", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_subscribe)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "unsubscribe", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_unsubscribe)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "publish", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_publish)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "cork", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_cork)); - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "ping", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_ping)); + static v8::CFunction fast_ping = v8::CFunction::Make(uWS_WebSocket_ping_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "ping", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_ping, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, &fast_ping)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemoteAddressAsText", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getRemoteAddressAsText)); - wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemotePort", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getRemotePort)); + static v8::CFunction fast_getRemotePort = v8::CFunction::Make(uWS_WebSocket_getRemotePort_fast); + wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemotePort", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_getRemotePort, Local(), Local(), 0, ConstructorBehavior::kAllow, SideEffectType::kHasNoSideEffect, &fast_getRemotePort)); wsTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "isSubscribed", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_WebSocket_isSubscribed)); /* This one does not exist in C++ */