The Stack and number types


WebAssembly execution is defined in terms of a stack machine. Some types of instructions pushe a number to a stack. Some instructions pops one number or numbers from the stack, execute and push the result to the stack or not.

For example, if you want to do addition, usei32.const to pushe a 32-bit integer onto the stack twice. i32.add pops two i32 numbers , computes their sum and pushes the resulting i32 number.

Of course, all processed data are bytes. What you push to the stack are bytes. What you pop from the stack are bytes. What the point of view you take for these bytes is the concept of data types. Once you have a data type, you can use concrete concepts to deal with data, not drop into 0 and 1 directly.

WebAssembly currently has four available types:

  • i32: 32-bit integer
  • i64: 64-bit integer
  • f32: 32-bit float
  • f64: 64-bit float

You might ask: are i32 and i64 signed or unsigned integers? The number type i32 and i64 are not inherently signed or unsigned. What you push or pop are bytes. The interpretation of these types is determined by individual operators.

For example, you can use i32.const to push a 32-bit number onto to the stack.

(module
    (func $main
        i32.const 2147483647
        drop
    )
    (start $main)
)

The binary representation of 2147483647 is 1111111 11111111 11111111 11111111. i32.const view these bytes as a 32-bit integers and use the little-endian order to push 01111111 11111111 11111111 11111111 to the stack.

The drop operator can be used to explicitly pop a number from the stack and drop the number directly. You use it here because a function is one type of block (explained in later documents). The $main function returns nothing so you have to empty the stack before leaving the function. If not, an error happens.

How about the number 2147483648?

(module
    (func $main
        i32.const 2147483648
        drop 
    )
    (start $main)
)

The binary representation of 2147483648 is 10000000 00000000 00000000 00000000. i32.const view these bytes as a 32-bit integers and use the little-endian order to push 10000000 00000000 00000000 00000000 to the stack. So, the above code has the same result as the following.

(module
    (func $main
        i32.const -2147483648
        drop 
    )
    (start $main)
)

WebAssembly uses 2's complement as a method of signed number representation so the binary representation of -2147483648 is also 10000000 00000000 00000000 00000000.

If you change i32 to i64:

(module
    (func $main
        i64.const 2147483648
        drop 
    )
    (start $main)
)

The binary representation of 2147483648 is 10000000 00000000 00000000 00000000. i64.const view these bytes as a 64-bit integers and use the little-endian order to push 00000000 00000000 00000000 00000000 10000000 00000000 00000000 00000000 to the stack.

When communicating with JavaScript, one thing you should note is, JavaScript stores numbers in double-precision 64-bit binary format IEEE 754. When representing integers, the maximum safe integer is 253 - 1 and the minimum safe integer is -(253 - 1). You cannot declare i64 when communicating with JavaScript. For example, the following code will has an error.

(module
    (import "env" "log" (func $log (param i64)))
    (func $main
        i64.const 2147483648
        call $log
    )
    (start $main)
)

Changing i64 to f64 solves the problem.

(module
    (import "env" "log" (func $log (param f64)))
    (func $main
        f64.const 2147483648
        call $log
    )
    (start $main)
)

Numbers can be written as decimals and hexidecimal. To input in hexdecimal notation, prefix with 0x. The floating point types can also be expressed using E or e for scientific notation. inf means infinity. nan means NaN (Not a number).