1
0
mirror of https://github.com/KarolS/millfork.git synced 2026-04-25 19:17:54 +00:00

Rename documentation files

This commit is contained in:
Karol Stasiak
2018-03-28 19:31:10 +02:00
parent 1fcbf9fd5b
commit 1a0737e4c9
21 changed files with 3 additions and 3 deletions
+184
View File
@@ -0,0 +1,184 @@
# Using assembly within Millfork programs
There are two ways to include raw assembly code in your Millfork programs:
* inline assembly code blocks
* whole assembly functions
## Assembly syntax
Millfork inline assembly uses the same three-letter opcodes as most other 6502 assemblers.
Indexing syntax is also the same. Only instructions available on the current CPU architecture are available.
**Work in progress**:
Currently, `RMBx`/`SMBx`/`BBRx`/`BBSx` and some extra 65CE02/HuC6280/65816 instructions are not supported yet.
Undocumented instructions are supported using various opcodes
Labels have to be followed by a colon and they can optionally be on a separate line.
Indentation is not important:
first: INC x
second:
INC y
INC z
Label names have to start with a letter and can contain digits, underscores and letters.
This means than they cannot start with a period like in many other assemblers.
Similarly, anonymous labels designated with `+` or `-` are also not supported
Labels are global,
which means that they live in the same namespace as functions, types and global variables.
Assembly can refer to variables and constants defined in Millfork,
but you need to be careful with using absolute vs immediate addressing:
const byte fiveConstant = 5
byte fiveVariable = 5
byte ten() {
byte result
asm {
LDA #fiveConstant
CLC
ADC fiveVariable
STA result
}
return result
}
Any assembly opcode can be prefixed with `?`, which allows the optimizer change it or elide it if needed.
Opcodes without that prefix will be always compiled as written.
You can insert macros into assembly, by prefixing them with `+` and using the same syntax as in Millfork:
macro void run(byte x) {
output = x
}
byte output @$c000
void main () {
byte a
a = 7
asm {
+ run(a)
}
}
Currently there is no way to insert raw bytes into inline assembly
(required for certain optimizations and calling conventions).
## Assembly functions
Assembly functions can be declared as `macro` or not.
A macro assembly function is inserted into the calling function like an inline assembly block,
and therefore usually it shouldn't end with `RTS` or `RTI`.
A non-macro assembly function should end with `RTS`, `JMP` or `RTI` as appropriate,
or it should be an external function.
For both macro and non-macro assembly functions,
the return type can be any valid return type, like for Millfork functions.
If the size of the return type is one byte,
then the result is passed via the accumulator.
If the size of the return type is two bytes,
then the low byte of the result is passed via the accumulator
and the high byte of the result is passed via the X register.
### Assembly function parameters
An assembly function can have parameters.
They differ from what is used by Millfork functions.
Macro assembly functions can have the following parameter types:
* reference parameters: `byte ref paramname`: every occurrence of the parameter will be replaced with the variable given as an argument
* constant parameters: `byte const paramname`: every occurrence of the parameter will be replaced with the constant value given as an argument
For example, if you have:
inline asm void increase(byte ref v, byte const inc) {
LDA v
CLC
ADC #inc
STA v
}
and call `increase(score, 10)`, the entire call will compile into:
LDA score
CLC
ADC #10
STA score
Non-macro functions can only have their parameters passed via registers:
* `byte a`, `byte x`, `byte y`: a single byte passed via the given CPU register
* `word xa`, `word ax`, `word ay`, `word ya`, `word xy`, `word yx`: a 2-byte word byte passed via given two CPU registers, with the high byte passed through the first register and the low byte passed through the second register
Macro assembly functions can have maximum one parameter passed via a register.
### External functions
An external function should be declared with a defined memory address
and the `extern` keyword instead of the body:
asm void putchar(byte a) @$FFD2 extern
## Safe assembly
Since assembly gives the programmer unlimited access to all machine features,
certain assumptions about the code may be broken.
In order to make assembly cooperate with the rest of the Millfork code,
it should abide to the following rules:
* don't leave the D flag set
* don't jump between functions if either of functions has stack variables
* don't do `RTS` or `RTI` if the function has stack variables
* don't jump or call things that are not functions or labels
* don't store data in locations other than variables or arrays
* don't change the stack pointer
* end non-inline assembly functions with `RTS`, `JMP` or `RTI` as appropriate
* on NMOS 6502:
* don't use `XAA`, `LXA`, `AHX`, `SHX`, `SHY`, `LAS` and `TAS` instructions
* on 65816:
* keep the direct page register set to $0000
* keep the M and X flags set to 1 (8-bit registers by default, native mode)
* if running in the native mode, be careful with the stack pointer (you should keep it between $000100 and $0001FF)
* do not change the data page register (keep an eye at the `PLD`, `MVN`, `MVP` instructions)
* explicitly use 16-bit immediate operands when appropriate; the assembler doesn't track flags and assumes 8-bit immediates by default
* use far jumps unless you're sure that the called function returns with an `RTS`
* on 65CE02:
* keep the `B` register set to $00
* don't change the `E` flag
* on HuC6280
* don't use the `SET` instruction
The above list is not exhaustive.
+45
View File
@@ -0,0 +1,45 @@
# Function definitions
Syntax:
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [@ <address>] { <body> }`
`[segment (<segment>)] asm <return_type> <name> ( <params> ) @ <address> extern`
* `<segment>`: segment name; if absent, then defaults to `default_code_segment` as defined for the platform
* `<modifiers>`: zero or more of the following:
* `asm` the function is written in assembly, not in Millfork (obligatory for `extern` functions),
see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions)
* `macro` the function is a macro,
see [Macros_and inlining#Macros](../abi/inlining.md#macros)
* `inline` the function should preferably be inlined
see [Macros_and inlining#Inlining](../abi/inlining.md#automatic_inlining.md)
* `noinline` the function should never be inlined
* `interrupt` the function is a hardware interrupt handler.
You are not allowed to call such functions directly.
The function cannot have parameters and the retrn type should be `void`.
* `kernal_interrupt` the function is an interrupt handler called from a generic vendor-provider hardware interrupt handler.
The hardware instruction handler is assumed to have preserved the CPU registers,
so this function only has to preserve the zeropage pseudoregisters.
An example is the Commodore 64 interrupt handler that calls the function at an address read from $314/$315.
Unline hardware handlers with `interrupt`, you can treat functions with `kernal_interrupt` like normal functions.
* `<return_type>` is a valid return type, see [Types](./types.md)
* `<params>` is a comma-separated list of parameters, in form `type name`. Allowed types are the same as for local variables.
* `<address>` is a constant expression that defines where in the memory the function is or will be located.
* `extern` is a keyword than marks functions that are not defined in the current program,
but are likely to be available at certain address in memory.
Such functions should be marked as written in assembly and should have their parameters passed through registers.
* `<body>` is a newline-separated list of either Millfork or assembly statements
+39
View File
@@ -0,0 +1,39 @@
# Interfacing with external code
## Calling external functions at a static address
To call an external function, you need to declare it as `asm extern`. For example:
```
asm void putchar(byte a) @$FFD2 extern
```
The function parameter will be passed via the accumulator,
the function itself is located in ROM at $FFD2. A call like this:
```
putchar(13)
```
will be compiled to something like this:
```
LDA #13
JSR $FFD2
```
For more details about how to pass parameters to `asm` functions,
see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions).
## Calling external functions at a dynamic address
To call a function that has its address calculated dynamically,
you just need to do the same as what you would do in assembly:
```
asm void call_function(byte a) {
JMP (function_address)
}
```
where `function_address` is a variable that contains the address of the function to call.
+46
View File
@@ -0,0 +1,46 @@
# Literals and initializers
## Numeric literals
Decimal: `1`, `10`
Binary: `%0101`, `0b101001`
Quaternary: `0q2131`
Octal: `0o172`
Hexadecimal: `$D323`, `0x2a2`
## String literals
String literals are surrounded with double quotes and followed by the name of the encoding:
"this is a string" ascii
Characters between the quotes are interpreted literally,
there are no ways to escape special characters or quotes.
Currently available encodings:
* `ascii` standard ASCII
* `pet` or `petscii` PETSCII (ASCII-like character set used by Commodore machines)
* `scr` Commodore screencodes
When programming for Commodore,
use `pet` for strings you're printing using standard I/O routines
and `scr` for strings you're copying to screen memory directly.
## Array initialisers
An array is initialized with either a string literal,
or a list of byte literals and strings, surrounded by brackets:
array a = [1, 2]
array b = "----" scr
array c = ["hello world!" ascii, 13]
Trailing commas (`[1, 2,]`) are not allowed.
+201
View File
@@ -0,0 +1,201 @@
# Operators
Unlike in high-level languages, operators in Millfork have limited applicability.
Not every well-formed expression is actually compilable.
Most expressions involving single bytes compile,
but for larger types usually you need to use in-place modification operators.
Further improvements to the compiler may increase the number of acceptable combinations.
Certain expressions require the commandline flag `-fzp-register` (`.ini` equivalent: `zeropage_register`) to be enabled.
They will be marked with (zpreg) next to them.
The flag is enabled by default, but you can disable it if you need it.
## Precedence
Millfork has different operator precedence compared to most other languages. From highest to lowest it goes:
* `*`, `*'`
* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>`
* `:`
* `==`, `!=`, `<`, `>`, `<=`, `>=`
* `&&`
* `||`
* assignment and in-place modification operators
You cannot use two different operators at the same precedence levels without using parentheses to disambiguate.
It is to prevent confusion about whether `a + b & c << d` means `(a + b) & (c << d)` `((a + b) & c) << d` or something else.
The only exceptions are `+` and `-`, and `+'` and `-'`.
They are interpreted as expected: `5 - 3 + 2 == 4` and `5 -' 3 +' 2 == 4`.
Note that you cannot mix `+'` and `-'` with `+` and `-`.
## Argument types
In the descriptions below, arguments to the operators are explained as follows:
* `byte` means any one-byte type
* `word` means any two-byte type, or a byte expanded to a word
* `long` means any type longer than two bytes, or a shorter type expanded to such length to match the other argument
* `constant` means a compile-time constant
* `simple` means either: a constant, a non-stack variable,
a pointer indexed with a constant, a pointer indexed with a non-stack variable,
an array indexed with a constant, an array indexed with a non-stack variable,
an array indexed with a sum of a constant and a non-stack variable,
or a split-word expression made of two simple expressions.
Examples: `1`, `a`, `p[2]`, `p[i]`, `arr[2]`, `arr[i]`, `arr[i+2]`, `h:l`, `h[i]:l[i]`
Such expressions have the property that the only register they may clobber is Y.
* `mutable` means an expression that can be assigned to
## Split-word operator
Expressions of the shape `h:l` where `h` and `l` are of type byte, are considered expressions of type word.
If and only if both `h` and `l` are assignable expressions, then `h:l` is also an assignable expression.
## Binary arithmetic operators
* `+`, `-`:
`byte + byte`
`constant word + constant word`
`constant long + constant long`
`constant word + byte`
`word + word` (zpreg)
* `*`: multiplication; the size of the result is the same as the size of the arguments
`byte * constant byte`
`constant byte * byte`
`constant word * constant word`
`constant long * constant long`
`byte * byte` (zpreg)
There are no division, remainder or modulo operators.
## Bitwise operators
* `|`, `^`, `&`: OR, EXOR and AND
`byte | byte`
`constant word | constant word`
`constant long | constant long`
`word | word` (zpreg)
* `<<`, `>>`: bit shifting; shifting pads the result with zeroes
`byte << byte`
`word << byte` (zpreg)
`constant word << constant byte`
`constant long << constant byte`
* `>>>>`: shifting a 9-bit value and returning a byte; `a >>>> b` is equivalent to `(a & $1FF) >> b`
`word >>>> constant byte`
## Decimal arithmetic operators
These operators work using the decimal arithmetic and will not work on Ricoh CPU's.
The compiler issues a warning if these operators appear in the code.
* `+'`, `-'`: decimal addition/subtraction
`byte +' byte`
`constant word +' constant word`
`constant long +' constant long`
`word +' word` (zpreg)
* `*'`: decimal multiplication
`constant *' constant`
* `<<'`, `>>'`: decimal multiplication/division by power of two
`byte <<' constant byte`
## Comparison operators
These operators (except for `!=`) can accept more than 2 arguments.
In such case, the result is true if each comparison in the group is true.
Note you cannot mix those operators, so `a <= b < c` is not valid.
Note that currently in cases like `a < f() < b`, `f()` will be evaluated twice!
* `==`: equality
`byte == byte`
`simple word == simple word`
`simple long == simple long`
* `!=`: inequality
`byte != byte`
`simple word != simple word`
`simple long != simple long`
* `>`, `<`, `<=`, `>=`: inequality
`byte > byte`
`simple word > simple word`
`simple long > simple long`
Currently, `>`, `<`, `<=`, `>=` operators perform unsigned comparison
if none of the types of their arguments is signed,
and fail to compile otherwise. This will be changed in the future.
## Assignment and in-place modification operators
* `=`: normal assignment
`mutable byte = byte`
`mutable word = word`
`mutable long = long`
* `+=`, `+'=`, `|=`, `^=`, `&=`: modification in place
`mutable byte += byte`
`mutable word += word`
`mutable long += long`
* `<<=`, `>>=`: shift in place
`mutable byte <<= byte`
`mutable word <<= byte`
`mutable long <<= byte`
* `<<'=`, `>>'=`: decimal shift in place
`mutable byte <<= constant byte`
`mutable word <<= constant byte`
`mutable long <<= constant byte`
* `-=`, `-'=`: subtraction in place
`mutable byte -= byte`
`mutable word -= simple word`
`mutable long -= simple long`
* `*=`: multiplication in place
`mutable byte *= constant byte`
`mutable byte *= byte` (zpreg)
* `*'=`: decimal multiplication in place
`mutable byte *'= constant byte`
## Indexing
While Millfork does not consider indexing an operator, this is a place as good as any to discuss it.
An expression of form `a[i]`, where `i` is an expression of type `byte`, is:
* when `a` is an array: an access to the `i`-th element of the array `a`
* when `a` is a pointer variable: an access to the byte in memory at address `a + i`
Those expressions are of type `byte`. If `a` is any other kind of expression, `a[i]` is invalid.
## Built-in functions
* `not`: negation of a boolean expression
`not(bool)`
* `nonet`: expansion of an 8-bit operation to a 9-bit operation
`nonet(byte + byte)`
`nonet(byte +' byte)`
`nonet(byte << constant byte)`
`nonet(byte <<' constant byte)`
Other kinds of expressions than the above (even `nonet(byte + byte + byte)`) will not work as expected.
+70
View File
@@ -0,0 +1,70 @@
# Reentrancy
A function is called reentrant,
when its execution can be interrupted and the function can be then safely called again.
When programming in Millfork, you need to distinguish conceptually three kinds of reentrant functions:
* nesting-safe
* recursion-safe
* interrupt-safe
As Millfork is a middle-level language, it leaves taking care of those issues to the programmer.
## Nesting safety
Nesting occurs when a function is called when calculating parameters for another call of the same function:
f(f(4))
f(0, f(1,1))
f(g(f(5))
f(g()) // where g calls f, directly or indirectly
Since parameters are passed via global variables,
calling a function while preparing parameters for another call to the same function may cause undefined behaviour.
For that reason, a function is considered nesting-safe if it has maximum one parameter.
It is possible to make a safe nested call to a non-nesting safe function, provided two conditions are met:
* the function cannot modify its parameters
* the non-nested parameters have to have the same values in all co-occurring calls: `f(5, f(5, 6, 7), 7)`
In all other cases, the nested call may cause undefined behaviour.
## Recursion safety
A function is recursive if it calls itself, either directly or indirectly.
Since most automatic variables will be overwritten by the inner call, the function is recursive-safe if:
* parameters are no longer read after the recursive call is made
* an automatic variable is not read from without reinitialization after each recursive call
* all the other variables are stack variables
In all other cases, the recursive call may cause undefined behaviour.
The easiest, but suboptimal way to make a function recursion-safe is to make all local variables stack-allocated
and assigning all parameters to variables as soon as possible. This is slow though, so don't do it unless really necessary.
## Interrupt safety
A function is interrupt-safe if it can be safely called, either directly or indirectly,
simultaneously by the main code and by an interrupt routine.
The only way to make a function interrupt-safe is to have no parameters and make all local variables stack-allocated.
# Reentrancy safety violations
Each of the following things is a violation of reentrancy safety rules and will cause undefined behaviour with high probability:
* calling a non-nesting-safe function without extra precautions as above while preparing another call to that function
* calling a non-recursion-safe function from within itself recursively
* calling a non-interrupt-safe function from both the main code and an interrupt
+222
View File
@@ -0,0 +1,222 @@
# Syntax
For information about types, see [Types](./types.md).
For information about literals, see [Literals](./literals.md).
For information about assembly, see [Using assembly within Millfork programs](./assembly.md).
## Comments
Comments start with `//` and last until the end of line.
## Declarations
### Variable declarations
A variable declaration can happen at either top level of a file (*global* variables),
or a top level of a function (*local* variables).
Syntax:
`[segment(<segment>)] [<storage>] <type> <name> [@<address>] [= <initial_value>]`
* `<segment>`: segment name; if absent, then defaults to `default`.
* `<storage>` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing.
`register` is only a hint for the optimizer.
See [the description of variable storage](../abi/variable-storage.md).
* `<address>` is a constant expression that defines where in the memory the variable will be located.
If not specified, it will be located according to the usual allocation rules.
`stack` variables cannot have a defined address.
* `<initial_value>` is a constant expression that contains the initial value of the variable.
Only global variables can be initialized that way.
The behaviour is undefined when targeting a ROM-based platform.
### Constant declarations
`const <type> <name> = <value>`
TODO
### Array declarations
An array is a continuous sequence of bytes in memory.
Syntax:
`[segment(<segment>)] array <name> [[<size>]] [@<address>] [= <initial_values>]`
* `<segment>`: segment name; if absent,
then defaults to `default_code_segment` as defined for the platform if the array has initial values,
or to `default` if it doesn't.
TODO
### Function declarations
A function can be declared at the top level. For more details, see [Functions](./functions.md)
## `import` statements
TODO
## Statements
### Expression statement
TODO
### `if` statement
Syntax:
```
if <expression> {
<body>
}
```
```
if <expression> {
<body>
} else {
<body>
}
```
### `return` statement
Syntax:
```
return
```
```
return <expression>
```
### `return[]` statement (return dispatch)
Syntax examples:
```
return [a + b] {
0 @ underflow
255 @ overflow
default @ nothing
}
```
```
return [getF()] {
1 @ function1
2 @ function2
default(5) @ functionDefault
}
```
```
return [i] (param1, param2) {
1,5,8 @ function1(4, 6)
2 @ function2(9)
default(0,20) @ functionDefault
}
```
Return dispatch calculates the value of an index, picks the correct branch,
assigns some global variables and jumps to another function.
The index has to evaluate to a byte. The functions cannot be `macro` and shouldn't have parameters.
Jumping to a function with parameters gives those parameters undefined values.
The functions are not called, so they don't return to the function the return dispatch statement is in, but to its caller.
The return values are passed along. If the dispatching function has a non-`void` return type different that the type
of the function dispatched to, the return value is undefined.
If the `default` branch exists, then it is used for every missing index value between other supported values.
Optional parameters to `default` specify the maximum, or both the minimum and maximum supported index value.
In the above examples: the first example supports values 0255, second 15, and third 020.
If the index has an unsupported value, the behaviour is formally undefined, but in practice the program will simply crash.
Before jumping to the function, the chosen global variables will be assigned parameter values.
Variables have to be global byte-sized. Some simple array indexing expressions are also allowed.
Parameter values have to be constants.
For example, in the third example one of the following will happen:
* if `i` is 1, 5 or 8, then `param1` is assigned 4, `param2` is assigned 6 and then `function1` is called;
* if `i` is 2, then `param1` is assigned 9, `param2` is assigned an undefined value and then `function2` is called;
* if `i` is any other value from 0 to 20, then `param1` and `param2` are assigned undefined values and then `functionDefault` is called;
* if `i` has any other value, then undefined behaviour.
### `while` and `do-while` statements
Syntax:
```
while <expression> {
<body>
}
```
```
do {
<body>
} while <expression>
```
### `for` statements
**Warning: `for` loops are a bit buggy.**
Syntax:
```
for <variable>,<start>,<direction>,<end> {
}
```
* `<variable>` an already defined numeric variable
* `<direction>` the range to traverse:
* `to` from `<start>` inclusive to `<end>` inclusive, in ascending order
(e.g. `0,to,9` to traverse 0, 1,... 9)
* `downto` from `<start>` inclusive to `<end>` inclusive, in descending order
(e.g. `9,downto,0` to traverse 9, 8,... 0)
* `until` from `<start>` inclusive to `<end>` exclusive, in ascending order
(e.g. `0,until,10` to traverse 0, 1,... 9)
* `parallelto` the same as `to`, but the iterations may be executed in any order
* `paralleluntil` the same as `until`, but the iterations may be executed in any order
There is no `paralleldownto`, because it would do the same as `parallelto`.
### `break` and `continue` statements
Syntax:
```
break
break for
break while
break do
break <variable>
continue
continue for
continue while
continue do
continue <variable>
```
### `asm` statements
See [Using assembly within Millfork programs](./assembly.md).
+36
View File
@@ -0,0 +1,36 @@
# Types
Millfork puts extra limitations on which types can be used in which contexts.
## Numeric types
* `byte` 1-byte value of undefined signedness, defaulting to unsigned
* `word` 2-byte value of undefined signedness, defaulting to unsigned
* `long` 4-byte value of undefined signedness, defaulting to unsigned
* `sbyte` signed 1-byte value
* `ubyte` unsigned 1-byte value
* `pointer` the same as `word`, but variables of this type default to be zero-page-allocated
and you can index `pointer` variables (not arbitrary `pointer`-typed expressions though, `f()[0]` won't compile)
Functions cannot return types longer than 2 bytes.
Numeric types can be converted automatically:
* from a smaller type to a bigger type (`byte``word`)
* from a type of undefined signedness to a type of defined signedness (`byte``sbyte`)
* from a type of defined signedness to a type of undefined signedness (`sbyte``byte`)
## Boolean types
TODO
## Special types
* `void` a unit type containing no information, can be only used as a return type for a function.