poorna2152 / nballerina

WebAssembly Backend for the nBallerina compiler
https://ballerina.io/
Apache License 2.0
4 stars 0 forks source link

List representation in WASM #22

Closed poorna2152 closed 2 years ago

poorna2152 commented 2 years ago

Using the array type described in the gc proposal to represent the lists.

Sample

import ballerina/io;

public function main() {
    io:println([1, 2, 3]); // @output [1,2,3]
}

Procedure:

(module
  (type $BoxedInt (struct (field $val i64))) 
  (type $any_list (array (mut anyref)))
  (import "console" "log" (func $println (param anyref))) 
  (export "main" (func $main)) 
  (export "tagged_to_int" (func $tagged_to_int)) 
  (export "get_type" (func $get_type)) 
  (export "len" (func $len)) 
  (export "array_get" (func $array_get)) 
  (func $main 
    (local $0 anyref) 
    (local $1 anyref) 
    (local $2 anyref)
    (local $3 (ref null $any_list))
    (block 
      (local.set $0
        (call $int_to_tagged
          (i64.const 1)))
      (local.set $1
        (call $int_to_tagged
          (i64.const 2)))
      (local.set $2
        (call $int_to_tagged
          (i64.const 3)))
      (local.set $3
        (array.new_default_with_rtt $any_list (i32.const 3) (rtt.canon $any_list)))
      (array.set 
        $any_list 
        (local.get $3) 
        (i32.const 0) 
        (local.get $0))
      (array.set 
        $any_list 
        (local.get $3)
        (i32.const 1)
        (local.get $1))
      (array.set 
        $any_list 
        (local.get $3)
        (i32.const 2)
        (local.get $2))
      (call $println 
        (local.get $3)) 
      (return)))
  (func $int_to_tagged (param $0 i64) (result anyref) 
    (return 
      (struct.new_with_rtt $BoxedInt 
        (local.get $0) 
        (rtt.canon $BoxedInt))))
  (func $tagged_to_int (param $0 anyref) (result i64) 
    (return 
      (struct.get $BoxedInt $val 
        (ref.cast 
          (ref.as_data 
            (local.get $0)) 
          (rtt.canon $BoxedInt)))))
  (func $len (param $0 anyref) (result i32)
    (array.len 
      $any_list
      (ref.cast
        (ref.as_data 
          (local.get $0)) 
        (rtt.canon $any_list))))
  (func $array_get (param $0 anyref) (param $1 i32) (result anyref)
    (array.get 
      $any_list  
      (ref.cast
        (ref.as_data 
          (local.get $0)) 
        (rtt.canon $any_list)) 
      (local.get $1)))
  (func $get_type (param $0 anyref) (result i32) 
    (if 
      (ref.is_i31 
        (local.get $0)) 
      (return 
        (i32.const 1)) 
      (if 
        (ref.is_null 
          (local.get $0)) 
        (return 
          (i32.const 2))
        (return
          (i32.const 0))))))
poorna2152 commented 2 years ago

Using this method doesn't allow us to push content to an array because array length is predefined. What can be done?

  1. Preallocate more space than needed.
  2. Create a new array when pushing to an array.
manuranga commented 2 years ago

Option 2 is the way to go, look at array in C runtime. we do the same there.

manuranga commented 2 years ago

Another option is to wrap javascript array shall we give that a try.

poorna2152 commented 2 years ago

Array provides gc support in wasm. If we use C or Js wouldn't it remove that advantage

manuranga commented 2 years ago

1) Not to use C, look at the algorithm we use to grow an array and replicate it in wasm. 2) JS should get GC shouldn't it? I am not sure how it happens when ref escapes to wasm land.

poorna2152 commented 2 years ago

If we are to create a new array when pushing to an array. Then we change the reference of the array. Then the following program fails.

import ballerina/io;
public function main() {
    any[] v = [0];
    any[] y = v;
    io:println(v === y); // @output true
    v.push(0);
    io:println(v === y); // @output true
}

The expected output should be true, true. But the output we get is true, false because when pushing reference gets changed.

manuranga commented 2 years ago

I think I didn't make myself clear enough in above 1. You will need to create a fixed length array and warp it with a another GC-able structure such as struct and return that. Provide a function that will accept an element and a wrapper (let's call this a list) and insert to the inner array. It should grow the inner array by some factor if it runs out, look at the ballerina runtime C code to get the exact starting length and factor.

poorna2152 commented 2 years ago

Current implementation: Using a struct to represent a list.

(type $List (struct (field $arr (mut (ref $AnyList))) (field $len (mut i64)))) 
(type $AnyList (array (mut eqref)))