task: primitive statements vs function calls

This commit is contained in:
Kartik K. Agaram 2021-10-26 23:12:57 -07:00
parent 5713eb7302
commit c7dde8d8b4
5 changed files with 98 additions and 1 deletions

8
mu.md
View File

@ -7,7 +7,7 @@ Mu programs are sequences of `fn` and `type` definitions.
Define functions with the `fn` keyword. For example:
```
fn foo arg1: int, arg2: int -> result/eax: boolean
fn foo arg1: int, arg2: int -> _/eax: boolean
```
Functions contain `{}` blocks, `var` declarations, primitive statements and
@ -36,6 +36,12 @@ Primitives can often write to arbitrary output registers. User-defined
functions, however, require rigidly specified output registers. Notice how the
definition of `foo` above writes to `eax`.
A function's header names its inouts, but not its outputs (hence the `_`
above).
All Mu programs must define at least one function: `main`. That's where they
begin executing instructions.
## Variables, registers and memory
Declare local variables in a function using the `var` keyword. As shown above,

View File

@ -288,6 +288,72 @@ var x/edx: int <- copy 0
Run `translate` (or `translate_emulated`) as usual. Use your runbook from Task
6 to address the errors that arise.
## Task 8: primitive statements vs function calls
Managing variables in memory vs register is one of two key skills to
programming in Mu. The second key skill is calling primitives (which are
provided by the x86 instruction set) vs functions (which are defined in terms
of primitives).
To prepare for this task, reread the very first section of the Mu reference,
on [functions and function calls](https://github.com/akkartik/mu/blob/main/mu.md#functions).
Now look at the following programs. In each case, write down whether you
expect translation to return any errors and why.
```
fn f a: int {
}
fn main {
f 0
var r/eax: int <- copy 3
f r
var m: int
f m
}
```
(When you're ready, try the above program out as `./translate tutorial/task8a.mu`.)
```
fn f -> _/eax: int {
var result/ecx: int <- copy 0
return result
}
fn main {
var x/eax: int <- f
}
```
(When you're ready, try the above program out as `./translate tutorial/task8b.mu`.)
```
fn f -> _/eax: int {
return 3
}
fn main {
var x/ecx: int <- f
}
```
(When you're ready, try the above program out as `./translate tutorial/task8c.mu`.)
Functions have fewer restrictions than primitives on inouts, but more
restrictions on outputs. Inouts can be registers, or memory, or even literals.
This is why the first example above is legal. Outputs, however, _must_
hard-code specific registers, and function calls must write their outputs to
matching registers. This is why the third example above is illegal.
One subtlety here is that we only require agreement on output registers
between function call and function header. We don't actually have to `return`
the precise register a function header specifies. The return value can even be
a literal integer or in memory somewhere. The `return` is really just a `copy`
to the appropriate register(s). This is why the second example above is legal.
## Task 9: operating with fractional numbers
All our variables so far have had type `int` (integer), but there are limits

10
tutorial/task8a.mu Normal file
View File

@ -0,0 +1,10 @@
fn f a: int {
}
fn main {
f 0
var r/eax: int <- copy 3
f r
var m: int
f m
}

8
tutorial/task8b.mu Normal file
View File

@ -0,0 +1,8 @@
fn f -> _/eax: int {
var result/ecx: int <- copy 0
return result
}
fn main {
var x/eax: int <- f
}

7
tutorial/task8c.mu Normal file
View File

@ -0,0 +1,7 @@
fn f -> _/eax: int {
return 3
}
fn main {
var x/ecx: int <- f
}