Something went wrong on our end
-
Noe Eric De Santo authoredNoe Eric De Santo authored
lab05.md 5.76 KiB
Demo
(func $Factorial_f (param i32 i32) (result i32) (local i32)
;;> fn f(i: Int(32), j: Int(32)): Int(32) = {
;;| val res: Int(32) =
;;| (i + j);
;;| res
;;| }
;;> i
local.get 0
;;> j
local.get 1
;;> (i + j)
i32.add
;;> val res: Int(32)
local.set 2
;;> res
local.get 2
)
(func $Factorial_fact (param i32) (result i32)
;;> fn fact(i: Int(32)): Int(32) = {
;;| (if((i < 2)) {
;;| 1
;;| } else {
;;| (i * fact((i - 1)))
;;| })
;;| }
;;> i
local.get 0
;;> 2
i32.const 2
;;> (i < 2)
i32.lt_s
;;> (if((i < 2)) {
;;| 1
;;| } else {
;;| (i * fact((i - 1)))
;;| })
if (result i32)
;;> 1
i32.const 1
else
;;> i
local.get 0
;;> fact((i - 1))
;;> i
local.get 0
;;> 1
i32.const 1
;;> (i - 1)
i32.sub
call $Factorial_fact
;;> (i * fact((i - 1)))
i32.mul
end
)
WASM basics
Stack machine
- WASM is a stack based machine.
- WASM has types. We will use exclusively i32.
- Instructions can push or pop values from the stack.
- i32.const x : push x to the stack.
- i32.add : pop 2 values, add them and push the result.
- drop : pop a value and ignore it.
- Locals can store values inside a function. Useful for val definitions among others.
- local.get x : get xth local
- local.set x : set xth local
- Globals store program wide values.
- global.get x : get xth global
- global.set x : set xth global
- Control flow.
- if : pop value from stack, if 0 goto else otherwise continue.
- call : pop arguments from the stack, jump to function.
Function calls
How to call a function:
- Push the required number of arguments on the stack.
- Call the function. The call instruction will pop the arguments and place them in the locals.
- The result will be placed on top of the stack.
(func $f (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
(
i32.const 3 ;; arg 0
i32.const 4 ;; arg 1
;; A
call $f
;; B
)
A:
| |
| 4 | <-- arg 1
| 3 | <-- arg 0
|-------|
B:
| |
| |
| 7 | <-- result
|-------|
Store
Store 3 at address 48
| |
| |
| |
|--------| <-- bottom of the stack
`i32.const 48`
| |
| |
| 48 | <-- address
|--------| <-- bottom of the stack
`i32.const 3`
| |
| 3 | <-- value
| 48 | <-- address
|--------| <-- bottom of the stack
`i32.store` pops 2 values from the stack
| |
| |
| |
|--------| <-- bottom of the stack
Heap
| address | 0 | 1 | 2 | .. | 47 | 48 | 49 | .. |
|---------|----|----|----|----|----|----|----|----|
| value | 0 | 0 | 0 | .. | 0 | 3 | 0 | .. |
^
value written
Values
Very similar to java.
- Ints are represented simply with an i32.
- Bools are represented with an i32, false = 0, true = 1.
- Unit is represented with an i32 with value 0.
Strings
| address | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
|---------|----|----|----|----|----|----|----|----|
| value | 104| 101| 108| 108| 111| 33 | 0 | 0 |
| ascii | h | e | l | l | o | ! | \0 | |
| |
| |
| 24 | <-- pointer to string
|--------| <-- bottom of the stack
ADTs
- store the value on the heap to reduced the size to the size of a pointer.
- store which constructor the value holds.
def getList(): List = { ... }
val ls: List = getList();
// What is the size of list here?
// Is it a Nil or a Cons?
Cons(42, Nil())
| address | value |
|---------|---------|
| 0 | 1 | \
| 1 | | | constructor id.
| 2 | | | Cons
| 3 | | /
| 4 | 42 | \
| 5 | | | first member: int
| 6 | | | 42
| 7 | | /
| 8 | 1234 | \
| 9 | | | seconder member: pointer to Nil
| 10 | | | 1234
| 11 | | /
Field offset = 4 + 4 * field number
==> Utils.scala:adtField
Allocation
Utils.scala:memoryBoundary is the index of a global variable that holds a pointer to the next free bytes.
Example in pseudocode:
Start of the program:
global.set(memoryBoundary, 0)
We want to allocate "hello!" = 7 bytes (don't forget the null terminator).
Store current memory pointer as pointer to our new string:
hello_string = global.get(memoryBoundary)
Increment the memory boundary by 7 (size of string).
global.set(memoryBoundary, global.get(memoryBoundary) + 7)
With webassembly instructions:
;; With memoryBoundary = 0.
;; Load the current boundary for string
global.get 0
;; Load it again for the arithmetic
global.get 0
;; length of string
i32.const 7
;; base + length = new boundary
i32.add
;; store new boundary
global.set 0
;; now the string pointer is on the stack, we just
;; need to copy the character's bytes into it.
...
Pattern matching
A pattern matching expression:
e match {
case p1 => e1
...
case pn => en
}
can be considered to be equivalent to the following pseudocode:
val v = e;
if (matchAndBind(v, p1)) e1
else if (matchAndBind(v, p2)) e2
else if ...
else if (matchAndBind(v, pn)) en
else error("Match error!")
matchAndBind is equivalent to this:
WildcardPattern:
"case _ => ..."
matchAndBind(v, _) = true
IdPattern:
"case id => ..."
matchAndBind(v, id) = { id = v; true }
LiteralPattern:
"case 3 => ..."
matchAndBind(v, lit) = { v == lit }
CaseClassPattern:
"case Cons(x, _) => ..."
matchAndBind(C_1(v_1, ..., v_n), C_2(p_1, ..., p_m)) = {
C_1 == C_2 &&
matchAndBind(v_1, p_1) &&
...
matchAndBind(v_m, p_m)
}