diff --git a/napi-inl.h b/napi-inl.h index 59f1ca14e..6078b02a6 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2278,6 +2278,33 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, return ArrayBuffer(env, value); } + +#if defined(__cpp_lib_span) +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, std::span span) { + return ArrayBuffer::New(env, span.data(), span.size_bytes()); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback) { + return ArrayBuffer::New( + env, span.data(), span.size_bytes(), std::move(finalizeCallback)); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint) { + return ArrayBuffer::New(env, + span.data(), + span.size_bytes(), + std::move(finalizeCallback), + finalizeHint); +} +#endif // defined(__cpp_lib_span) #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { @@ -2311,6 +2338,13 @@ inline size_t ArrayBuffer::ByteLength() { return length; } +#if defined(__cpp_lib_span) +template +inline std::span ArrayBuffer::Span() { + return std::span(reinterpret_cast(Data()), ByteLength() / sizeof(T)); +} +#endif // defined(__cpp_lib_span) + #if NAPI_VERSION >= 7 inline bool ArrayBuffer::IsDetached() const { bool detached; @@ -2764,6 +2798,18 @@ inline const T* TypedArrayOf::Data() const { return _data; } +#if defined(__cpp_lib_span) +template +inline std::span TypedArrayOf::Span() { + return std::span(Data(), ElementLength()); +} + +template +inline std::span TypedArrayOf::Span() const { + return std::span(Data(), ElementLength()); +} +#endif // defined(__cpp_lib_span) + //////////////////////////////////////////////////////////////////////////////// // Function class //////////////////////////////////////////////////////////////////////////////// @@ -3176,6 +3222,34 @@ inline Buffer Buffer::New(napi_env env, } return Buffer(env, value); } + +#if defined(__cpp_lib_span) +template +template +inline Buffer Buffer::New(napi_env env, std::span span) { + return Buffer::New(env, span.data(), span.size()); +} + +template +template +inline Buffer Buffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback) { + return Buffer::New( + env, span.data(), span.size(), std::move(finalizeCallback)); +} + +template +template +inline Buffer Buffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint) { + return Buffer::New( + env, span.data(), span.size(), std::move(finalizeCallback), finalizeHint); +} +#endif // defined(__cpp_lib_span) + #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED template @@ -3274,6 +3348,46 @@ inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { return Buffer(env, value); } +#if defined(__cpp_lib_span) +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, std::span span) { + return Buffer::NewOrCopy(env, span.data(), span.size()); +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback) { + return Buffer::NewOrCopy( + env, span.data(), span.size(), std::move(finalizeCallback)); +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint) { + return Buffer::NewOrCopy( + env, span.data(), span.size(), std::move(finalizeCallback), finalizeHint); +} + +template +template +inline Buffer Buffer::Copy(napi_env env, + std::span span) { + return Buffer::Copy(env, span.data(), span.size()); +} + +template +template +inline Buffer Buffer::Copy(napi_env env, std::span span) { + return Buffer::Copy(env, span.data(), span.size()); +} +#endif // defined(__cpp_lib_span) + template inline void Buffer::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); @@ -3301,6 +3415,13 @@ inline T* Buffer::Data() const { return reinterpret_cast(const_cast(Uint8Array::Data())); } +#if defined(__cpp_lib_span) +template +inline std::span Buffer::Span() const { + return std::span(Data(), Length()); +} +#endif // defined(__cpp_lib_span) + //////////////////////////////////////////////////////////////////////////////// // Error class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 6b4692c7d..28dd0946d 100644 --- a/napi.h +++ b/napi.h @@ -23,6 +23,11 @@ #include #include #include +#if defined(__has_include) +#if __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +#include +#endif +#endif // VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known // good version) @@ -1364,6 +1369,22 @@ class ArrayBuffer : public Object { Hint* finalizeHint ///< Hint (second parameter) to be passed to the ///< finalize callback ); + +#if defined(__cpp_lib_span) + template + static ArrayBuffer New(napi_env env, std::span span); + + template + static ArrayBuffer New(napi_env env, + std::span span, + Finalizer finalizeCallback); + + template + static ArrayBuffer New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // defined(__cpp_lib_span) #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED static void CheckCast(napi_env env, napi_value value); @@ -1375,6 +1396,11 @@ class ArrayBuffer : public Object { void* Data(); ///< Gets a pointer to the data buffer. size_t ByteLength(); ///< Gets the length of the array buffer in bytes. +#if defined(__cpp_lib_span) + template + std::span Span(); +#endif // defined(__cpp_lib_span) + #if NAPI_VERSION >= 7 bool IsDetached() const; void Detach(); @@ -1557,6 +1583,11 @@ class TypedArrayOf : public TypedArray { /// `ArrayBuffer`. const T* Data() const; +#if defined(__cpp_lib_span) + std::span Span(); + std::span Span() const; +#endif // defined(__cpp_lib_span) + private: T* _data; @@ -1781,6 +1812,22 @@ class Buffer : public Uint8Array { size_t length, Finalizer finalizeCallback, Hint* finalizeHint); + +#if defined(__cpp_lib_span) + template + static Buffer New(napi_env env, std::span span); + + template + static Buffer New(napi_env env, + std::span span, + Finalizer finalizeCallback); + + template + static Buffer New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // defined(__cpp_lib_span) #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED static Buffer NewOrCopy(napi_env env, T* data, size_t length); @@ -1799,6 +1846,28 @@ class Buffer : public Uint8Array { Finalizer finalizeCallback, Hint* finalizeHint); +#if defined(__cpp_lib_span) + template + static Buffer NewOrCopy(napi_env env, std::span span); + + template + static Buffer NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback); + + template + static Buffer NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); + + template + static Buffer Copy(napi_env env, std::span span); + + template + static Buffer Copy(napi_env env, std::span span); +#endif // defined(__cpp_lib_span) + static Buffer Copy(napi_env env, const T* data, size_t length); static void CheckCast(napi_env env, napi_value value); @@ -1808,6 +1877,10 @@ class Buffer : public Uint8Array { size_t Length() const; T* Data() const; +#if defined(__cpp_lib_span) + std::span Span() const; +#endif // defined(__cpp_lib_span) + private: }; diff --git a/test/array_buffer.cc b/test/array_buffer.cc index 2b07bd250..5e26caef0 100644 --- a/test/array_buffer.cc +++ b/test/array_buffer.cc @@ -220,6 +220,42 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { } } +#if defined(__cpp_lib_span) +Value CreateBufferFromSpan(const CallbackInfo& info) { + std::span span(testData, testLength); + ArrayBuffer buffer = ArrayBuffer::New(info.Env(), span); + if (buffer.ByteLength() != testLength || buffer.Data() != testData) { + Error::New(info.Env(), "Incorrect array buffer created from span.") + .ThrowAsJavaScriptException(); + return Value(); + } + InitData(testData, testLength); + return buffer; +} + +Value CheckArrayBufferSpan(const CallbackInfo& info) { + ArrayBuffer buffer = info[0].As(); + std::span span = buffer.Span(); + if (span.size() != testLength || span.data() != buffer.Data()) { + Error::New(info.Env(), "Incorrect array buffer span.") + .ThrowAsJavaScriptException(); + return Boolean::New(info.Env(), false); + } + return Boolean::New(info.Env(), true); +} + +Value CheckTypedArraySpan(const CallbackInfo& info) { + Uint8Array array = info[0].As(); + std::span span = array.Span(); + if (span.size() != testLength || span.data() != array.Data()) { + Error::New(info.Env(), "Incorrect typed array span.") + .ThrowAsJavaScriptException(); + return Boolean::New(info.Env(), false); + } + return Boolean::New(info.Env(), true); +} +#endif // defined(__cpp_lib_span) + } // end anonymous namespace Object InitArrayBuffer(Env env) { @@ -239,5 +275,14 @@ Object InitArrayBuffer(Env env) { exports["checkDetachUpdatesData"] = Function::New(env, CheckDetachUpdatesData); +#if defined(__cpp_lib_span) + exports["hasSpanSupport"] = Boolean::New(env, true); + exports["createBufferFromSpan"] = Function::New(env, CreateBufferFromSpan); + exports["checkArrayBufferSpan"] = Function::New(env, CheckArrayBufferSpan); + exports["checkTypedArraySpan"] = Function::New(env, CheckTypedArraySpan); +#else + exports["hasSpanSupport"] = Boolean::New(env, false); +#endif + return exports; } diff --git a/test/array_buffer.js b/test/array_buffer.js index 08547ed7e..d44107db5 100644 --- a/test/array_buffer.js +++ b/test/array_buffer.js @@ -65,6 +65,19 @@ function test (binding) { // Let C++ detach the ArrayBuffer. const extBuffer = binding.arraybuffer.createExternalBuffer(); binding.arraybuffer.checkDetachUpdatesData(extBuffer); + }, + + 'std::span ArrayBuffer and TypedArray tests', + () => { + if (!binding.arraybuffer.hasSpanSupport) return; + + const fromSpan = binding.arraybuffer.createBufferFromSpan(); + binding.arraybuffer.checkBuffer(fromSpan); + assert.ok(fromSpan instanceof ArrayBuffer); + assert.ok(binding.arraybuffer.checkArrayBufferSpan(fromSpan)); + + const uint8 = new Uint8Array(fromSpan); + assert.ok(binding.arraybuffer.checkTypedArraySpan(uint8)); } ]); } diff --git a/test/binding.gyp b/test/binding.gyp index 9ff334b64..381d5e153 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -118,6 +118,21 @@ 'sources': ['>@(build_sources)'], 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] }, + { + 'target_name': 'binding_cpp20', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'], + 'cflags_cc': ['-std=c++20'], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'AdditionalOptions': ['/std:c++20'] + } + }, + 'xcode_settings': { + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++20' + } + }, { 'target_name': 'binding_except_all', 'dependencies': ['../node_addon_api.gyp:node_addon_api_except_all'], diff --git a/test/buffer.cc b/test/buffer.cc index c10300dc7..cfff78e7e 100644 --- a/test/buffer.cc +++ b/test/buffer.cc @@ -160,6 +160,55 @@ Value GetFinalizeCount(const CallbackInfo& info) { return Number::New(info.Env(), finalizeCount); } +#if defined(__cpp_lib_span) +Value CreateBufferFromSpan(const CallbackInfo& info) { + std::span span(testData, testLength); + Buffer buffer = Buffer::New(info.Env(), span); + if (buffer.Length() != testLength || buffer.Data() != testData) { + Error::New(info.Env(), "Incorrect buffer created from span.") + .ThrowAsJavaScriptException(); + return Value(); + } + InitData(testData, testLength); + return buffer; +} + +Value CreateBufferCopyFromSpan(const CallbackInfo& info) { + InitData(testData, testLength); + std::span span(testData, testLength); + Buffer buffer = Buffer::Copy(info.Env(), span); + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length from span copy.") + .ThrowAsJavaScriptException(); + return Value(); + } + return buffer; +} + +Value CreateBufferNewOrCopyFromSpan(const CallbackInfo& info) { + std::span span(testData, testLength); + Buffer buffer = Buffer::NewOrCopy(info.Env(), span); + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length from NewOrCopy span.") + .ThrowAsJavaScriptException(); + return Value(); + } + InitData(testData, testLength); + return buffer; +} + +Value CheckBufferSpan(const CallbackInfo& info) { + Buffer buffer = info[0].As>(); + std::span span = buffer.Span(); + if (span.size() != testLength || span.data() != buffer.Data()) { + Error::New(info.Env(), "Incorrect buffer span.") + .ThrowAsJavaScriptException(); + return Boolean::New(info.Env(), false); + } + return Boolean::New(info.Env(), true); +} +#endif // defined(__cpp_lib_span) + } // end anonymous namespace Object InitBuffer(Env env) { @@ -181,5 +230,17 @@ Object InitBuffer(Env env) { exports["checkBuffer"] = Function::New(env, CheckBuffer); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); +#if defined(__cpp_lib_span) + exports["hasSpanSupport"] = Boolean::New(env, true); + exports["createBufferFromSpan"] = Function::New(env, CreateBufferFromSpan); + exports["createBufferCopyFromSpan"] = + Function::New(env, CreateBufferCopyFromSpan); + exports["createBufferNewOrCopyFromSpan"] = + Function::New(env, CreateBufferNewOrCopyFromSpan); + exports["checkBufferSpan"] = Function::New(env, CheckBufferSpan); +#else + exports["hasSpanSupport"] = Boolean::New(env, false); +#endif + return exports; } diff --git a/test/buffer.js b/test/buffer.js index 3f49201a7..3165df5a0 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -25,6 +25,26 @@ function test (binding) { assert.ok(test instanceof Buffer); }, + 'std::span Buffer tests', + () => { + if (!binding.buffer.hasSpanSupport) return; + + const fromSpan = binding.buffer.createBufferFromSpan(); + binding.buffer.checkBuffer(fromSpan); + assert.ok(fromSpan instanceof Buffer); + assert.ok(binding.buffer.checkBufferSpan(fromSpan)); + + const copyFromSpan = binding.buffer.createBufferCopyFromSpan(); + binding.buffer.checkBuffer(copyFromSpan); + assert.ok(copyFromSpan instanceof Buffer); + assert.ok(binding.buffer.checkBufferSpan(copyFromSpan)); + + const newOrCopySpan = binding.buffer.createBufferNewOrCopyFromSpan(); + binding.buffer.checkBuffer(newOrCopySpan); + assert.ok(newOrCopySpan instanceof Buffer); + assert.ok(binding.buffer.checkBufferSpan(newOrCopySpan)); + }, + 'External Buffer', () => { const test = binding.buffer.createExternalBuffer(); diff --git a/test/common/index.js b/test/common/index.js index 2469151ef..0fe628272 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -161,12 +161,15 @@ exports.whichBuildType = whichBuildType; exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); + const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) - ].map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) + ].filter(it => fs.existsSync(path.resolve(__dirname, it))) + .map(it => require.resolve(it)); for (const item of bindings) { await Promise.resolve(test(require(item), { bindingPath: item })) @@ -177,12 +180,15 @@ exports.runTest = async function (test, buildType, buildPathRoot = process.env.B exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); + const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) - ].map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) + ].filter(it => fs.existsSync(path.resolve(__dirname, it))) + .map(it => require.resolve(it)); for (const item of bindings) { await test(item);