poorna2152 / nballerina

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

`wback` Exception Handling #12

Closed poorna2152 closed 2 years ago

poorna2152 commented 2 years ago

WebAssembly provides try, catch, catchall, throw, rethrow and delegate instructions for handling exceptions. However the exceptions which can be handled by the catch and catchall blocks are the exceptions which are explicitly thrown using the throw instruction. In the following program if the second parameter is 0 it is expected to throw a division by zero error. But this error is not caught by the catchall block. It is caught by the runtime.

(module
  (type $i64_=>_none (func (param i64)))
  (type $i64_i64_=>_none (func (param i64 i64)))
  (import "console" "log" (func $println (param i64)))
  (export "testFunc" (func $testFunc))
    (func $testFunc (param $0 i64) (param $1 i64) (local $2 i64)
      (try
        (do
          (local.set $2
            (i64.div_s
              (local.get $0)
              (local.get $1)))
          (call $println
            (local.get $2)))
        (catch_all
          (call $println
            (i64.const -1))))))

Following works because we are explicitly throwing the error when second parameter is zero.

(module
  (type $i64_=>_none (func (param i64)))
  (type $i64_i64_=>_none (func (param i64 i64)))
  (import "console" "log" (func $println (param i64)))
  (tag $a-tag)
  (export "testFunc" (func $testFunc))
  (func $testFunc (param $0 i64) (param $1 i64)
    (try
      (do
        (if
          (i64.eq
            (local.get $1)
            (i64.const 0))
          (throw $a-tag)
          (call $println
            (i64.div_s
            (local.get $0)
            (local.get $1)))))
      (catch $a-tag
        (call $println
          (i64.const -1))))))
poorna2152 commented 2 years ago

Exceptions can be handled using the try catch in the JS file. Considering the div3-p.bal.

poorna2152 commented 2 years ago

When outside this range it is expected to throw an Error. But in WASM it does not throw an error for this.(Behaves in a circular way: 9223372036854775807 + 1 = -9223372036854775808). Neither does Javascript. The BigInt in JavaScript is used to represent the i64 value send to console.log. BigInt does not have a upperbound on the numbers it can represent Therefore should the handling of this Exception be done on the WAT code.

if op1 > 0 && op2 > 0 && result < 0 
  throw "overflow"
manuranga commented 2 years ago

1) Debug line info options: a) Differ this for a later subset. b) "Bake-in" the lines as part of error creation c) Use https://yurydelendik.github.io/webassembly-dwarf/ Lets go with a) for now

2) Overflow detection https://github.com/WebAssembly/design/blob/main/FutureFeatures.md#integer-overflow-detection I think we'll have to do a per-check, but your proposed per-check is wrong. Please take a look at https://www.cs.utah.edu/~regehr/papers/overflow12.pdf Clang has support for this, but produced code looks ugly https://godbolt.org/z/o9bYTM1db maybe it's best one can do? not sure.

manuranga commented 2 years ago

btw ballerina ints can have the value -9223372036854775808

poorna2152 commented 2 years ago

Following checks for overflow,

poorna2152 commented 2 years ago

The output WAT file for the add2-p.bal.

(module 
  (import "console" "log" (func $println (param i64)))
  (tag $overflow)
  (export "main" (func $main))
  (export "add" (func $add))
  (func $main
    (local $0 i64)
    (local $1 i64)
    (local $2 i32)
    (block 
      (block 
        (local.set $0
          (i64.const 9223372036854775807))
        (local.set $1
          (call $add
            (local.get $0)
            (i64.const 1)))
        (call $println
          (local.get $1))
        (return ))))
  (func $add  (param $0 i64)  (param $1 i64) (result i64)
    (local $2 i64)
    (local $3 i32)
    (block 
      (block 
        (try 
          (do
            (block 
              (if 
                (i32.and 
                  (i64.gt_s 
                    (local.get $0)
                    (i64.const 0))
                  (i64.gt_s 
                    (local.get $1)
                    (i64.const 0)))
                (block 
                  (if 
                    (i64.gt_s 
                      (local.get $0)
                        (i64.sub 
                          (i64.const 9223372036854775807)
                          (local.get $1)))
                    (block 
                      (throw $overflow))))
                (block 
                  (if 
                    (i32.and 
                      (i64.lt_s 
                        (local.get $0)
                        (i64.const 0))
                      (i64.lt_s 
                        (local.get $1)
                        (i64.const 0)))
                    (block
                      (if 
                        (i64.lt_s 
                          (local.get $0)
                          (i64.sub 
                            (i64.const -9223372036854775808)
                            (local.get $1)))
                        (block 
                          (throw $overflow)))))))
              (local.set $2
                (i64.add 
                  (local.get $0)
                  (local.get $1))))))
        (return
          (local.get $2))))))

Is this the approach expected. If so this leads to duplicate code in the generated WAT file. Should we create a function for each of the add, sub, multiply and divide operations which check for overflows and call those on arithmetic operations.

poorna2152 commented 2 years ago

Possible methods,

poorna2152 commented 2 years ago