Skip to content

Commit

Permalink
src: add encoding_methods with fast api
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Dec 6, 2022
1 parent 50fb246 commit 1f41306
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 82 deletions.
20 changes: 15 additions & 5 deletions benchmark/util/text-encoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@

const common = require('../common.js');

const BASE = 'string\ud801';

const bench = common.createBenchmark(main, {
len: [256, 1024, 1024 * 32],
len: [0, 256, 1024, 1024 * 32],
n: [1e4],
type: ['v8-one-byte-string', 'v8-two-byte-string'],
op: ['encode', 'encodeInto']
});

function main({ n, op, len }) {
function main({ n, op, len, type }) {
const encoder = new TextEncoder();
const input = BASE.repeat(len);
let base = '';

switch (type) {
case 'v8-one-byte-string':
base = 'a';
break;
case 'v8-two-byte-string':
base = 'ğ';
break;
}

const input = base.repeat(len);
const subarray = new Uint8Array(len);

bench.start();
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const internalBindingAllowlist = new SafeSet([
'constants',
'contextify',
'crypto',
'encoding_methods',
'fs',
'fs_event_wrap',
'http_parser',
Expand Down
18 changes: 14 additions & 4 deletions lib/internal/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,14 @@ const {
} = require('internal/validators');

const {
encodeInto,
encodeUtf8String,
decodeUTF8,
} = internalBinding('buffer');

const {
encodeUtf8,
encodeIntoUtf8,
} = internalBinding('encoding_methods');

let Buffer;
function lazyBuffer() {
if (Buffer === undefined)
Expand Down Expand Up @@ -337,15 +340,22 @@ class TextEncoder {

encode(input = '') {
validateEncoder(this);
return encodeUtf8String(`${input}`);
input = `${input}`;
if (input.length > 0) {
return encodeUtf8(input);
}
return new Uint8Array([]);
}

encodeInto(src, dest) {
validateEncoder(this);
validateString(src, 'src');
if (!dest || !isUint8Array(dest))
throw new ERR_INVALID_ARG_TYPE('dest', 'Uint8Array', dest);
encodeInto(src, dest, encodeIntoResults);
if (src.length === 0) {
return { read: 0, written: 0 };
}
encodeIntoUtf8(src, dest, encodeIntoResults);
return { read: encodeIntoResults[0], written: encodeIntoResults[1] };
}

Expand Down
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@
'src/node_contextify.cc',
'src/node_credentials.cc',
'src/node_dir.cc',
'src/node_encoding.cc',
'src/node_env_var.cc',
'src/node_errors.cc',
'src/node_external_reference.cc',
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
V(contextify) \
V(credentials) \
V(errors) \
V(encoding_methods) \
V(fs) \
V(fs_dir) \
V(fs_event_wrap) \
Expand Down
73 changes: 0 additions & 73 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1146,73 +1146,6 @@ void Swap64(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(args[0]);
}


// Encode a single string to a UTF-8 Uint8Array (not Buffer).
// Used in TextEncoder.prototype.encode.
static void EncodeUtf8String(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_GE(args.Length(), 1);
CHECK(args[0]->IsString());

Local<String> str = args[0].As<String>();
size_t length = str->Utf8Length(isolate);

Local<ArrayBuffer> ab;
{
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
std::unique_ptr<BackingStore> bs =
ArrayBuffer::NewBackingStore(isolate, length);

CHECK(bs);

str->WriteUtf8(isolate,
static_cast<char*>(bs->Data()),
-1, // We are certain that `data` is sufficiently large
nullptr,
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);

ab = ArrayBuffer::New(isolate, std::move(bs));
}

auto array = Uint8Array::New(ab, 0, length);
args.GetReturnValue().Set(array);
}


static void EncodeInto(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_GE(args.Length(), 3);
CHECK(args[0]->IsString());
CHECK(args[1]->IsUint8Array());
CHECK(args[2]->IsUint32Array());

Local<String> source = args[0].As<String>();

Local<Uint8Array> dest = args[1].As<Uint8Array>();
Local<ArrayBuffer> buf = dest->Buffer();
char* write_result = static_cast<char*>(buf->Data()) + dest->ByteOffset();
size_t dest_length = dest->ByteLength();

// results = [ read, written ]
Local<Uint32Array> result_arr = args[2].As<Uint32Array>();
uint32_t* results = reinterpret_cast<uint32_t*>(
static_cast<char*>(result_arr->Buffer()->Data()) +
result_arr->ByteOffset());

int nchars;
int written = source->WriteUtf8(
isolate,
write_result,
dest_length,
&nchars,
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);
results[0] = nchars;
results[1] = written;
}


void SetBufferPrototype(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand Down Expand Up @@ -1344,9 +1277,6 @@ void Initialize(Local<Object> target,
SetMethod(context, target, "swap32", Swap32);
SetMethod(context, target, "swap64", Swap64);

SetMethod(context, target, "encodeInto", EncodeInto);
SetMethodNoSideEffect(context, target, "encodeUtf8String", EncodeUtf8String);

target
->Set(context,
FIXED_ONE_BYTE_STRING(isolate, "kMaxLength"),
Expand Down Expand Up @@ -1399,9 +1329,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(Swap32);
registry->Register(Swap64);

registry->Register(EncodeInto);
registry->Register(EncodeUtf8String);

registry->Register(StringSlice<ASCII>);
registry->Register(StringSlice<BASE64>);
registry->Register(StringSlice<BASE64URL>);
Expand Down
147 changes: 147 additions & 0 deletions src/node_encoding.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "env-inl.h"
#include "node.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "util-inl.h"
#include "v8-fast-api-calls.h"
#include "v8.h"

#if defined(NODE_HAVE_I18N_SUPPORT)
#include <unicode/utf8.h>
#endif // NODE_HAVE_I18N_SUPPORT

namespace node {

using v8::ArrayBuffer;
using v8::BackingStore;
using v8::CFunction;
using v8::Context;
using v8::FastApiCallbackOptions;
using v8::FastApiTypedArray;
using v8::FastOneByteString;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Uint32Array;
using v8::Uint8Array;
using v8::Value;

namespace encoding_methods {

static void EncodeUtf8(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_GE(args.Length(), 1);
CHECK(args[0]->IsString());

Local<String> str = args[0].As<String>();
size_t length = str->Utf8Length(isolate);

Local<ArrayBuffer> ab;
{
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
std::unique_ptr<BackingStore> bs =
ArrayBuffer::NewBackingStore(isolate, length);

CHECK(bs);

str->WriteUtf8(isolate,
static_cast<char*>(bs->Data()),
-1, // We are certain that `data` is sufficiently large
nullptr,
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);

ab = ArrayBuffer::New(isolate, std::move(bs));
}

args.GetReturnValue().Set(Uint8Array::New(ab, 0, length));
}

static void EncodeIntoUtf8(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_GE(args.Length(), 3);
CHECK(args[0]->IsString());
CHECK(args[1]->IsUint8Array());
CHECK(args[2]->IsUint32Array());

Local<String> source = args[0].As<String>();

Local<Uint8Array> dest = args[1].As<Uint8Array>();
Local<ArrayBuffer> buf = dest->Buffer();
char* write_result = static_cast<char*>(buf->Data()) + dest->ByteOffset();
size_t dest_length = dest->ByteLength();

// results = [ read, written ]
Local<Uint32Array> result_arr = args[2].As<Uint32Array>();
uint32_t* results = reinterpret_cast<uint32_t*>(
static_cast<char*>(result_arr->Buffer()->Data()) +
result_arr->ByteOffset());

int nchars;
int written = source->WriteUtf8(
isolate,
write_result,
dest_length,
&nchars,
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);
results[0] = nchars;
results[1] = written;
}

static void FastEncodeIntoUtf8(
Local<Value> receiver,
const FastOneByteString& source,
const FastApiTypedArray<uint8_t>& destination,
const FastApiTypedArray<uint32_t>& result,
FastApiCallbackOptions& options) { // NOLINT(runtime/references)
#if defined(NODE_HAVE_I18N_SUPPORT)
uint8_t* destination_data;
CHECK(destination.getStorageIfAligned(&destination_data));

uint32_t* results;
CHECK(result.getStorageIfAligned(&results));

size_t destination_length = destination.length();
size_t source_length = source.length;
size_t written = std::min(source_length, destination_length);

U8_GET_UNSAFE(source.data, 0, *results);
results[0] = source_length;
results[1] = written;
#else
options.fallback = true;
#endif // NODE_HAVE_I18N_SUPPORT
}

CFunction fast_encode_into_utf8_(CFunction::Make(FastEncodeIntoUtf8));

static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
SetMethodNoSideEffect(context, target, "encodeUtf8", EncodeUtf8);
SetFastMethod(context,
target,
"encodeIntoUtf8",
EncodeIntoUtf8,
&fast_encode_into_utf8_);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(EncodeUtf8);

registry->Register(EncodeIntoUtf8);
registry->Register(FastEncodeIntoUtf8);
registry->Register(fast_encode_into_utf8_.GetTypeInfo());
}

} // namespace encoding_methods
} // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(encoding_methods,
node::encoding_methods::Initialize)
NODE_BINDING_EXTERNAL_REFERENCE(
encoding_methods, node::encoding_methods::RegisterExternalReferences)
9 changes: 9 additions & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@

namespace node {

// TODO(anonrig): Find a good way of reusing existing types for fast api usages.
using CFunctionCallback = void (*)(v8::Local<v8::Value> receiver);
using CFunctionCallbackWithInput = void (*)(
v8::Local<v8::Value> receiver,
const v8::FastOneByteString& source,
const v8::FastApiTypedArray<uint8_t>& destination,
const v8::FastApiTypedArray<uint32_t>& result,
v8::FastApiCallbackOptions& options); // NOLINT(runtime/references)

// This class manages the external references from the V8 heap
// to the C++ addresses in Node.js.
Expand All @@ -20,6 +27,7 @@ class ExternalReferenceRegistry {

#define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \
V(CFunctionCallback) \
V(CFunctionCallbackWithInput) \
V(const v8::CFunctionInfo*) \
V(v8::FunctionCallback) \
V(v8::AccessorGetterCallback) \
Expand Down Expand Up @@ -67,6 +75,7 @@ class ExternalReferenceRegistry {
V(credentials) \
V(env_var) \
V(errors) \
V(encoding_methods) \
V(fs) \
V(fs_dir) \
V(fs_event_wrap) \
Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-bootstrap-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const expectedModules = new Set([
'Internal Binding contextify',
'Internal Binding credentials',
'Internal Binding errors',
'Internal Binding encoding_methods',
'Internal Binding fs_dir',
'Internal Binding fs_event_wrap',
'Internal Binding fs',
Expand Down
Loading

0 comments on commit 1f41306

Please sign in to comment.