DEV Community

jtenner
jtenner

Posted on • Updated on

AssemblyScript std lib Functions: ArrayBuffer#constructor

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)