Today I am starting a series of posts on the AssemblyScript std library functions to discuss how the functions are implemented. Today's function is the ArrayBuffer
constructor
function.
ArrayBuffer
This class represents a raw and sized heap allocation that contains data. This method is very small but contains a lot of moving parts worth discussing. Here, you can see the implementation directly from the std lib source.
export class ArrayBuffer {
constructor(length: i32) {
if (<u32>length > <u32>BLOCK_MAXSIZE) throw new RangeError(E_INVALIDLENGTH);
var buffer = __alloc(<usize>length, idof<ArrayBuffer>());
memory.fill(buffer, 0, <usize>length);
return changetype<ArrayBuffer>(buffer); // retains
}
}
Here's an example of how to use it.
// create a heap allocation of 12 bytes
let buffer = new ArrayBuffer(12);
The first statement is an if
statement that asserts the provided length of the ArrayBuffer
does not exceed the BLOCK_MAXSIZE
compile-time constant. If it does, the proceeding statement throws a RangeError
to indicate that the requested block size is too big.
if (<u32>length > <u32>BLOCK_MAXSIZE) throw new RangeError(E_INVALIDLENGTH);
The next statement is a variable declaration for buffer
which is initialized to an intrinsic heap allocation pointer. The __alloc()
function is a special built-in function that creates a memory block on the heap that contains some metadata about the block size and block type along with the data itself. Its parameters are the heap allocation size and the runtime "type" of the heap allocation. In this case, the idof<ArrayBuffer>()
built-in function will return the proper runtime id for ArrayBuffer
. Finally, the __alloc()
function will return a pointer to the start of the data.
var buffer = __alloc(<usize>length, idof<ArrayBuffer>()); // returns `usize`
The statement after the buffer allocation is a memory.fill()
bulk call which sets all of the bytes in the ArrayBuffer
to the byte value of 0
. This effectively "zeroes" out the data. The memory.fill()
function takes three parameters: the pointer, the fill value (u8
), and the byteCount (usize
).
memory.fill(buffer, 0, <usize>length);
Finally, the constructor returns the pointer, but it uses a special changetype<T>(value: any)
function. Typically, the use of any
is prohibited in AssemblyScript, but this particular function signals to AssemblyScript that it needs to turn the pointer into a reference type.
At this point, it will cause the runtime reference counter to start paying attention to the buffer
object. The // retains
comment is a reminder that AssemblyScript is garbage collected using reference counting, and will now start to pay attention to the pointer just like any other ArrayBuffer
.
return changetype<ArrayBuffer>(buffer); // retains
If you are interested in what ArrayBuffer#constructor
compiles to in unoptimized web assembly, you can look here.
(func $~lib/arraybuffer/ArrayBuffer#constructor (; 18 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
local.get $1
i32.const 1073741808
i32.gt_u
if
i32.const 24
i32.const 72
i32.const 57
i32.const 42
call $~lib/builtins/abort
unreachable
end
local.get $1
i32.const 0
call $~lib/rt/tlsf/__alloc
local.set $2
local.get $2
i32.const 0
local.get $1
call $~lib/memory/memory.fill
local.get $2
call $~lib/rt/pure/__retain
)
All attempts to find an optimized version were thwarted by binaryen
because it tends to inline this particular function very often.
Have any comments or questions? Feel free to leave them below!
Thanks for reading,
@jtenner
Top comments (0)