[wasm] add experimental string/Wasm GC array conversion fast-path
This CL adds two experimental JS builtins to convert between i16 Wasm GC and JS strings. This is a non-standard experimental feature only available with the flag --wasm-gc-js-interop. WebAssembly.experimentalConvertArrayToString(array, start, count) Convert the `count`-many WTF-16 code units starting at index `start` into a JS string. Throws a TypeError if `array` is not an i16 array, or if `start` and `count` are not numbers or not in range. WebAssembly.experimentalConvertStringToArray(string, sampleArray) Convert `string` to an i16 array. The `sampleArray` parameter needs to be an arbitrary i16 array, which is only used to extract the rtt. Throws a TypeError if `string` is not a string or `sampleArray` is not an i16 array. Change-Id: I7ac2f6bd89b8f638427f61da1bb01ccba90d735b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3642301 Commit-Queue: Tobias Tebbi <tebbi@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/main@{#80505}
This commit is contained in:
parent
334cc5f8d6
commit
b16d2a4e2f
@ -200,6 +200,10 @@ struct SliceIterator<T: type, Reference: type> {
|
||||
return *this.NextReference() otherwise NoMore;
|
||||
}
|
||||
|
||||
macro NextNotEmpty(): T {
|
||||
return *this.NextReferenceNotEmpty();
|
||||
}
|
||||
|
||||
macro NextReference(): Reference labels NoMore {
|
||||
if (this.Empty()) {
|
||||
goto NoMore;
|
||||
@ -210,6 +214,13 @@ struct SliceIterator<T: type, Reference: type> {
|
||||
}
|
||||
}
|
||||
|
||||
macro NextReferenceNotEmpty(): Reference {
|
||||
dcheck(!this.Empty());
|
||||
const result = unsafe::NewReference<T>(this.object, this.start);
|
||||
this.start += %SizeOf<T>();
|
||||
return result;
|
||||
}
|
||||
|
||||
object: HeapObject|TaggedZeroPattern;
|
||||
start: intptr;
|
||||
end: intptr;
|
||||
|
@ -315,7 +315,7 @@ builtin WasmAllocateStructWithRtt(rtt: Map): HeapObject {
|
||||
|
||||
macro WasmAllocateArray(
|
||||
rtt: Map, length: uint32, elementSize: uint32,
|
||||
initializationMode: constexpr InitializationMode): HeapObject {
|
||||
initializationMode: constexpr InitializationMode): WasmArray {
|
||||
// instanceSize = RoundUp(elementSize * length, kObjectAlignment)
|
||||
// + WasmArray::kHeaderSize
|
||||
const instanceSize: intptr =
|
||||
@ -328,7 +328,7 @@ macro WasmAllocateArray(
|
||||
// TODO(ishell): consider removing properties_or_hash field from WasmObjects.
|
||||
%RawDownCast<WasmArray>(result).properties_or_hash = kEmptyFixedArray;
|
||||
%RawDownCast<WasmArray>(result).length = length;
|
||||
return result;
|
||||
return %RawDownCast<WasmArray>(result);
|
||||
}
|
||||
|
||||
builtin WasmAllocateArray_Uninitialized(
|
||||
@ -663,4 +663,105 @@ builtin ThrowWasmTrapArrayOutOfBounds(): JSAny {
|
||||
builtin ThrowWasmTrapArrayTooLarge(): JSAny {
|
||||
tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapArrayTooLarge));
|
||||
}
|
||||
|
||||
macro TryNumberToIntptr(value: JSAny): intptr labels Failure {
|
||||
typeswitch (value) {
|
||||
case (s: Smi): {
|
||||
return Convert<intptr>(s);
|
||||
}
|
||||
case (hn: HeapNumber): deferred {
|
||||
let value = Convert<float64>(hn);
|
||||
if (Float64IsNaN(value)) goto Failure;
|
||||
value = math::Float64Trunc(value);
|
||||
return ChangeFloat64ToIntPtr(value);
|
||||
}
|
||||
case (JSAny): deferred {
|
||||
goto Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro WasmTypeInfo(map: Map): WasmTypeInfo {
|
||||
return UnsafeCast<WasmTypeInfo>(
|
||||
map.constructor_or_back_pointer_or_native_context);
|
||||
}
|
||||
|
||||
const kWasmI16ValueType: constexpr int32
|
||||
generates 'wasm::ValueType::Primitive(wasm::kI16).raw_bit_field()';
|
||||
|
||||
const kWasmArrayTypeRepOffset:
|
||||
constexpr intptr generates 'wasm::ArrayType::kRepOffset';
|
||||
const kWasmValueTypeBitFieldOffset:
|
||||
constexpr intptr generates 'wasm::ValueType::kBitFieldOffset';
|
||||
|
||||
macro IsWord16WasmArrayMap(map: Map): bool {
|
||||
const arrayTypePtr: RawPtr<int32> = %RawDownCast<RawPtr<int32>>(
|
||||
WasmTypeInfo(map).foreign_address_ptr + kWasmArrayTypeRepOffset +
|
||||
kWasmValueTypeBitFieldOffset);
|
||||
const arrayTypeRef: &int32 =
|
||||
torque_internal::unsafe::NewOffHeapReference(arrayTypePtr);
|
||||
return *arrayTypeRef == kWasmI16ValueType;
|
||||
}
|
||||
|
||||
// Non-standard experimental feature.
|
||||
transitioning javascript builtin ExperimentalWasmConvertArrayToString(
|
||||
js-implicit context: NativeContext)(
|
||||
array: JSAny, start: JSAny, count: JSAny): String {
|
||||
try {
|
||||
const start = TryNumberToIntptr(start) otherwise goto InvalidArgument;
|
||||
const count = TryNumberToIntptr(count) otherwise goto InvalidArgument;
|
||||
|
||||
const array = Cast<WasmArray>(array) otherwise goto InvalidArgument;
|
||||
if (!IsWord16WasmArrayMap(array.map)) goto InvalidArgument;
|
||||
const arrayContent = torque_internal::unsafe::NewConstSlice<char16>(
|
||||
array, kWasmArrayHeaderSize, Convert<intptr>(array.length));
|
||||
|
||||
return AllocateSeqTwoByteString(
|
||||
Convert<uint32>(Unsigned(count)),
|
||||
(Subslice(arrayContent, start, count) otherwise goto InvalidArgument)
|
||||
.Iterator());
|
||||
} label InvalidArgument deferred {
|
||||
ThrowTypeError(MessageTemplate::kInvalidArgument);
|
||||
}
|
||||
}
|
||||
|
||||
// Non-standard experimental feature.
|
||||
transitioning javascript builtin ExperimentalWasmConvertStringToArray(
|
||||
js-implicit context: NativeContext)(
|
||||
string: JSAny, sampleArray: JSAny): WasmArray {
|
||||
try {
|
||||
const sampleArray =
|
||||
Cast<WasmArray>(sampleArray) otherwise goto InvalidArgument;
|
||||
const arrayMap = sampleArray.map;
|
||||
if (!IsWord16WasmArrayMap(arrayMap)) goto InvalidArgument;
|
||||
|
||||
const string = Cast<String>(string) otherwise goto InvalidArgument;
|
||||
const length = string.length;
|
||||
|
||||
const result = WasmAllocateArray(
|
||||
arrayMap, Unsigned(length), 2, InitializationMode::kUninitialized);
|
||||
const arrayContent = torque_internal::unsafe::NewMutableSlice<char16>(
|
||||
result, kWasmArrayHeaderSize, Convert<intptr>(length));
|
||||
try {
|
||||
StringToSlice(string) otherwise OneByte, TwoByte;
|
||||
} label OneByte(slice: ConstSlice<char8>) {
|
||||
let fromIt = slice.Iterator();
|
||||
let toIt = arrayContent.Iterator();
|
||||
while (true) {
|
||||
let toRef = toIt.NextReference() otherwise break;
|
||||
*toRef = %RawDownCast<char16>(Convert<uint16>(fromIt.NextNotEmpty()));
|
||||
}
|
||||
} label TwoByte(slice: ConstSlice<char16>) {
|
||||
let fromIt = slice.Iterator();
|
||||
let toIt = arrayContent.Iterator();
|
||||
while (true) {
|
||||
let toRef = toIt.NextReference() otherwise break;
|
||||
*toRef = fromIt.NextNotEmpty();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} label InvalidArgument deferred {
|
||||
ThrowTypeError(MessageTemplate::kInvalidArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -572,19 +572,6 @@ V8_NOINLINE Handle<JSFunction> InstallFunctionWithBuiltinId(
|
||||
return fun;
|
||||
}
|
||||
|
||||
V8_NOINLINE Handle<JSFunction> SimpleInstallFunction(
|
||||
Isolate* isolate, Handle<JSObject> base, const char* name, Builtin call,
|
||||
int len, bool adapt, PropertyAttributes attrs = DONT_ENUM) {
|
||||
// Although function name does not have to be internalized the property name
|
||||
// will be internalized during property addition anyway, so do it here now.
|
||||
Handle<String> internalized_name =
|
||||
isolate->factory()->InternalizeUtf8String(name);
|
||||
Handle<JSFunction> fun =
|
||||
SimpleCreateFunction(isolate, internalized_name, call, len, adapt);
|
||||
JSObject::AddProperty(isolate, base, internalized_name, fun, attrs);
|
||||
return fun;
|
||||
}
|
||||
|
||||
V8_NOINLINE Handle<JSFunction> InstallFunctionAtSymbol(
|
||||
Isolate* isolate, Handle<JSObject> base, Handle<Symbol> symbol,
|
||||
const char* symbol_string, Builtin call, int len, bool adapt,
|
||||
@ -908,6 +895,21 @@ Handle<Map> CreateNonConstructorMap(Isolate* isolate, Handle<Map> source_map,
|
||||
|
||||
} // namespace
|
||||
|
||||
Handle<JSFunction> SimpleInstallFunction(Isolate* isolate,
|
||||
Handle<JSObject> base,
|
||||
const char* name, Builtin call,
|
||||
int len, bool adapt,
|
||||
PropertyAttributes attrs) {
|
||||
// Although function name does not have to be internalized the property name
|
||||
// will be internalized during property addition anyway, so do it here now.
|
||||
Handle<String> internalized_name =
|
||||
isolate->factory()->InternalizeUtf8String(name);
|
||||
Handle<JSFunction> fun =
|
||||
SimpleCreateFunction(isolate, internalized_name, call, len, adapt);
|
||||
JSObject::AddProperty(isolate, base, internalized_name, fun, attrs);
|
||||
return fun;
|
||||
}
|
||||
|
||||
void Genesis::CreateIteratorMaps(Handle<JSFunction> empty) {
|
||||
// Create iterator-related meta-objects.
|
||||
Handle<JSObject> iterator_prototype = factory()->NewJSObject(
|
||||
|
@ -129,6 +129,10 @@ class BootstrapperActive final {
|
||||
Bootstrapper* bootstrapper_;
|
||||
};
|
||||
|
||||
V8_NOINLINE Handle<JSFunction> SimpleInstallFunction(
|
||||
Isolate* isolate, Handle<JSObject> base, const char* name, Builtin call,
|
||||
int len, bool adapt, PropertyAttributes attrs = DONT_ENUM);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -139,11 +139,15 @@ class ArrayType : public ZoneObject {
|
||||
return rep_ != other.rep_ || mutability_ != other.mutability_;
|
||||
}
|
||||
|
||||
static const intptr_t kRepOffset;
|
||||
|
||||
private:
|
||||
const ValueType rep_;
|
||||
const bool mutability_;
|
||||
};
|
||||
|
||||
inline constexpr intptr_t ArrayType::kRepOffset = offsetof(ArrayType, rep_);
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -550,6 +550,8 @@ class ValueType {
|
||||
static constexpr int kKindBits = 5;
|
||||
static constexpr int kHeapTypeBits = 20;
|
||||
|
||||
static const intptr_t kBitFieldOffset;
|
||||
|
||||
private:
|
||||
// {hash_value} directly reads {bit_field_}.
|
||||
friend size_t hash_value(ValueType type);
|
||||
@ -576,6 +578,9 @@ class ValueType {
|
||||
uint32_t bit_field_;
|
||||
};
|
||||
|
||||
inline constexpr intptr_t ValueType::kBitFieldOffset =
|
||||
offsetof(ValueType, bit_field_);
|
||||
|
||||
static_assert(sizeof(ValueType) <= kUInt32Size,
|
||||
"ValueType is small and can be passed by value");
|
||||
static_assert(ValueType::kLastUsedBit < 8 * sizeof(ValueType) - kSmiTagSize,
|
||||
|
@ -2797,6 +2797,17 @@ void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
|
||||
InstallFunc(isolate, webassembly, "validate", WebAssemblyValidate, 1);
|
||||
InstallFunc(isolate, webassembly, "instantiate", WebAssemblyInstantiate, 1);
|
||||
|
||||
// TODO(tebbi): Put this behind its own flag once --wasm-gc-js-interop gets
|
||||
// closer to shipping.
|
||||
if (FLAG_wasm_gc_js_interop) {
|
||||
SimpleInstallFunction(
|
||||
isolate, webassembly, "experimentalConvertArrayToString",
|
||||
Builtin::kExperimentalWasmConvertArrayToString, 0, true);
|
||||
SimpleInstallFunction(
|
||||
isolate, webassembly, "experimentalConvertStringToArray",
|
||||
Builtin::kExperimentalWasmConvertStringToArray, 0, true);
|
||||
}
|
||||
|
||||
if (FLAG_wasm_test_streaming) {
|
||||
isolate->set_wasm_streaming_callback(WasmStreamingCallbackForTesting);
|
||||
}
|
||||
|
47
test/mjsunit/wasm/gc-experimental-string-conversions.js
Normal file
47
test/mjsunit/wasm/gc-experimental-string-conversions.js
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --experimental-wasm-gc --wasm-gc-js-interop
|
||||
|
||||
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
|
||||
|
||||
var builder = new WasmModuleBuilder();
|
||||
|
||||
let i16Array = builder.addArray(kWasmI16, true);
|
||||
|
||||
builder.addFunction('getHelloArray', makeSig([], [kWasmAnyRef]))
|
||||
.addBody([
|
||||
...wasmI32Const(72), ...wasmI32Const(69), ...wasmI32Const(76),
|
||||
...wasmI32Const(76), ...wasmI32Const(79), kGCPrefix, kExprArrayInitStatic,
|
||||
i16Array, 5
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
builder.addFunction('getChar', makeSig([kWasmAnyRef, kWasmI32], [kWasmI32]))
|
||||
.addBody([
|
||||
kExprLocalGet, 0, kGCPrefix, kExprRefAsData, kGCPrefix,
|
||||
kExprRefCastStatic, i16Array, kExprLocalGet, 1, kGCPrefix, kExprArrayGetS,
|
||||
i16Array
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
const instance = builder.instantiate();
|
||||
const getHelloArray = instance.exports.getHelloArray;
|
||||
const getChar = instance.exports.getChar;
|
||||
|
||||
assertEquals(
|
||||
WebAssembly.experimentalConvertArrayToString(getHelloArray(), 0, 5),
|
||||
'HELLO');
|
||||
assertEquals(
|
||||
WebAssembly.experimentalConvertArrayToString(getHelloArray(), 1, 4),
|
||||
'ELLO');
|
||||
assertEquals(
|
||||
WebAssembly.experimentalConvertArrayToString(getHelloArray(), 0, 3), 'HEL');
|
||||
|
||||
const string = 'foobar'
|
||||
const array =
|
||||
WebAssembly.experimentalConvertStringToArray('foobar', getHelloArray());
|
||||
for (let i = 0; i < string.length; ++i) {
|
||||
assertEquals(getChar(array, i), string.charCodeAt(i));
|
||||
}
|
Loading…
Reference in New Issue
Block a user