Browse Source

Implement stage 3 feature "arraybuffer-transfer" (#3649)

pull/3650/head
José Julián Espina 9 months ago committed by GitHub
parent
commit
76e2b7c93d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 163
      core/engine/src/builtins/array_buffer/mod.rs
  2. 5
      test262_config.toml

163
core/engine/src/builtins/array_buffer/mod.rs

@ -307,7 +307,12 @@ impl IntrinsicObject for ArrayBuffer {
.name(js_string!("get maxByteLength")) .name(js_string!("get maxByteLength"))
.build(); .build();
BuiltInBuilder::from_standard_constructor::<Self>(realm) #[cfg(feature = "experimental")]
let get_detached = BuiltInBuilder::callable(realm, Self::get_detached)
.name(js_string!("get detached"))
.build();
let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_accessor( .static_accessor(
JsSymbol::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
@ -339,8 +344,24 @@ impl IntrinsicObject for ArrayBuffer {
JsSymbol::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
#[cfg(feature = "experimental")]
let builder = builder
.accessor(
js_string!("detached"),
Some(get_detached),
None,
flag_attributes,
) )
.build(); .method(Self::transfer::<false>, js_string!("transfer"), 0)
.method(
Self::transfer::<true>,
js_string!("transferToFixedLength"),
0,
);
builder.build();
} }
fn get(intrinsics: &Intrinsics) -> JsObject { fn get(intrinsics: &Intrinsics) -> JsObject {
@ -505,6 +526,30 @@ impl ArrayBuffer {
Ok(JsValue::from(!buf.is_fixed_len())) Ok(JsValue::from(!buf.is_fixed_len()))
} }
/// [`get ArrayBuffer.prototype.detached`][spec].
///
/// [spec]: https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
#[cfg(feature = "experimental")]
fn get_detached(
this: &JsValue,
_args: &[JsValue],
_context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
let buf = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ()
.with_message("get ArrayBuffer.prototype.detached called with invalid `this`")
})?;
// 4. Return IsDetachedBuffer(O).
Ok(buf.is_detached().into())
}
/// [`ArrayBuffer.prototype.resize ( newLength )`][spec]. /// [`ArrayBuffer.prototype.resize ( newLength )`][spec].
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
@ -675,6 +720,116 @@ impl ArrayBuffer {
Ok(new.upcast().into()) Ok(new.upcast().into())
} }
/// [`ArrayBuffer.prototype.transfer ( [ newLength ] )`][transfer] and
/// [`ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )`][transferFL]
///
/// [transfer]: https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
/// [transferFL]: https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
#[cfg(feature = "experimental")]
fn transfer<const TO_FIXED_LENGTH: bool>(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Return ? ArrayBufferCopyAndDetach(O, newLength, preserve-resizability).
// Abstract operation `ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability )`
// https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffercopyanddetach
let new_length = args.get_or_undefined(0);
// 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]).
// 2. If IsSharedArrayBuffer(arrayBuffer) is true, throw a TypeError exception.
let buf = this
.as_object()
.and_then(|o| o.clone().downcast::<Self>().ok())
.ok_or_else(|| {
JsNativeError::typ().with_message(if TO_FIXED_LENGTH {
"ArrayBuffer.prototype.transferToFixedLength called with invalid `this`"
} else {
"ArrayBuffer.prototype.transfer called with invalid `this`"
})
})?;
// 3. If newLength is undefined, then
let new_len = if new_length.is_undefined() {
// a. Let newByteLength be arrayBuffer.[[ArrayBufferByteLength]].
buf.borrow().data.len() as u64
} else {
// 4. Else,
// a. Let newByteLength be ? ToIndex(newLength).
new_length.to_index(context)?
};
// 5. If IsDetachedBuffer(arrayBuffer) is true, throw a TypeError exception.
let Some(mut bytes) = buf.borrow_mut().data.data.take() else {
return Err(JsNativeError::typ()
.with_message("cannot transfer a detached buffer")
.into());
};
// 6. If preserveResizability is preserve-resizability and IsResizableArrayBuffer(arrayBuffer)
// is true, then
// a. Let newMaxByteLength be arrayBuffer.[[ArrayBufferMaxByteLength]].
// 7. Else,
// a. Let newMaxByteLength be empty.
let new_max_len = buf.borrow().data.max_byte_len.filter(|_| !TO_FIXED_LENGTH);
// 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a TypeError exception.
if !buf.borrow().data.detach_key.is_undefined() {
buf.borrow_mut().data.data = Some(bytes);
return Err(JsNativeError::typ()
.with_message("cannot transfer a buffer with a detach key")
.into());
}
// Effectively, the next steps only create a new object for the same vec, so we can skip all
// those steps and just make a single check + trigger the realloc.
// 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, newMaxByteLength).
// 10. Let copyLength be min(newByteLength, arrayBuffer.[[ArrayBufferByteLength]]).
// 11. Let fromBlock be arrayBuffer.[[ArrayBufferData]].
// 12. Let toBlock be newBuffer.[[ArrayBufferData]].
// 13. Perform CopyDataBlockBytes(toBlock, 0, fromBlock, 0, copyLength).
// 14. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are
// observable. Implementations may implement this method as a zero-copy move or a realloc.
// 15. Perform ! DetachArrayBuffer(arrayBuffer).
// 16. Return newBuffer.
if let Some(new_max_len) = new_max_len {
if new_len > new_max_len {
buf.borrow_mut().data.data = Some(bytes);
return Err(JsNativeError::range()
.with_message("`length` cannot be bigger than `maxByteLength`")
.into());
}
// Should only truncate without reallocating.
bytes.resize(new_len as usize, 0);
} else {
bytes.resize(new_len as usize, 0);
// Realloc the vec to fit onto the new exact length.
bytes.shrink_to_fit();
}
let prototype = context
.intrinsics()
.constructors()
.array_buffer()
.prototype();
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ArrayBuffer {
data: Some(bytes),
max_byte_len: new_max_len,
detach_key: JsValue::undefined(),
},
)
.into())
}
/// `AllocateArrayBuffer ( constructor, byteLength )` /// `AllocateArrayBuffer ( constructor, byteLength )`
/// ///
/// More information: /// More information:
@ -771,9 +926,7 @@ pub(crate) fn create_byte_data_block(
if alloc_size > context.host_hooks().max_buffer_size(context) { if alloc_size > context.host_hooks().max_buffer_size(context) {
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message( .with_message("cannot allocate a buffer that exceeds the maximum buffer size")
"cannot allocate a buffer that exceeds the maximum buffer size".to_string(),
)
.into()); .into());
} }

5
test262_config.toml

@ -1,4 +1,4 @@
commit = "e4f91b6381d7694265031caad0c71d733ac132f3" commit = "584048ed081d85f5eed6e884a7b40b6f4bcd67d7"
[ignored] [ignored]
# Not implemented yet: # Not implemented yet:
@ -35,9 +35,6 @@ features = [
# https://github.com/tc39/proposal-json-modules # https://github.com/tc39/proposal-json-modules
"json-modules", "json-modules",
# https://github.com/tc39/proposal-arraybuffer-transfer
"arraybuffer-transfer",
# https://github.com/tc39/proposal-realms # https://github.com/tc39/proposal-realms
"ShadowRealm", "ShadowRealm",

Loading…
Cancel
Save