diff --git a/docs/README.md b/docs/README.md index 0ce8210f..21917873 100644 --- a/docs/README.md +++ b/docs/README.md @@ -79,6 +79,14 @@ * [Game Boy–only modules](stdlib/gb.md) * [X16–only modules](stdlib/x16.md) + +## Guides + +* [Differences from C](various/cdiff.md) + +* [Differences from other assemblers](various/asmdiff.md) + +* [Optimization guide](various/optimization.md) ## Implementation details diff --git a/docs/doc_index.md b/docs/doc_index.md index 7450d0cd..42b40102 100644 --- a/docs/doc_index.md +++ b/docs/doc_index.md @@ -81,6 +81,14 @@ * [Game Boy–only modules](stdlib/gb.md) * [X16–only modules](stdlib/x16.md) + +## Guides + +* [Differences from C](various/cdiff.md) + +* [Differences from other assemblers](various/asmdiff.md) + +* [Optimization guide](various/optimization.md) ## Implementation details diff --git a/docs/various/asmdiff.md b/docs/various/asmdiff.md new file mode 100644 index 00000000..e3efc369 --- /dev/null +++ b/docs/various/asmdiff.md @@ -0,0 +1,34 @@ +[< back to index](../doc_index.md) + +# Differences in assembly + +## Syntax + +* High and low bytes of an 16-bit value are acquired using the `hi` and `lo` functions, not the `>` and `<` operators. + +* Anonymous labels and local labels are not supported. +All labels defined in assembly are global. +Colons are required in label declarations. + +* Macros are inserted using the `+` operator. + +* Raw bytes are inserted using the Millfork array syntax, not with any pseudoopcode (like `!byte`, `db` or `fcb`) + +* Assembly blocks cannot contain definitions of constants or variables. + +* 6502: To enforce zero-page addressing, wrap the argument in the `lo` function: `lo(arg)` + +* 6502: To enforce absolute addressing, add a 16-bit zero to the argument: `0000 + arg` + +* GameBoy: The $FF page loads/stores are written `LDH (C),A`, not `LD ($FF00+C),A`. + +* GameBoy: The loads/stores that postincrement/postdecrement HL write the HL register as `HLI` or `HLD`, not `HL+` or `HL-` + +* Z80: Indexing using the index register uses the `IX(1)` syntax, not `(IX+1)` or `1(IX)`. + +* Z80: Most undocumented instructions are not supported. The only one supported is `SLL`. + +* 6809: `0,X` is assembled the same as `,X`. + + + diff --git a/docs/various/cdiff.md b/docs/various/cdiff.md new file mode 100644 index 00000000..0dfee5a7 --- /dev/null +++ b/docs/various/cdiff.md @@ -0,0 +1,78 @@ +[< back to index](../doc_index.md) + +# Differences from C + +## Syntax + +* Block comments `/* */` are not supported, use line comments `//`. + +* You cannot put multiple statements on one line. +Semicolons are allowed at the end of the line, but no code can follow them. + +* There are no `++` or `--` operators. Use `+= 1` and `-= 1`. + +* Pointer types are declared using the `pointer.` prefix, not `*` suffix. To dereference them, use `p[0]` instead of `*p`. + +* There is no unary `&` operator. +Pointers to an object are acquired using the `.pointer` suffix. +Raw addresses are acquired using the `.addr` suffix. +The numeric values of the pointer and of the raw address may differ. + +* Operator precedence works differently. +Bitwise and bitshift operators have the same precedence as arithmetic operators, +and mixing different operators with the same precedence is usually forbidden. +This prevents most ambiguities in bit-twiddling code, but requires care when porting code from or to C. + +* There is no `!` operator. The negation is expressed as the `not` function. + +* The modulo operator is written `%%`. As with `/`, it's only defined for unsigned division. + +* The `for` loops are range-based. Arbitrary, C-like `for` loops are not supported. + +* Variable declaration and initialization have to be separate. + +* Integer literals starting with zero and containing just digits are decimal, not octal. +For octal literals, use the `0o` prefix. + +* String literals are not null-terminated by default. Use the `z` suffix for null-terminated strings. + +* In `if`, `do/while`, `while` and `for` statements, parentheses are not required, but braces are. +The `else` branch also requires braces, unless the only statement in the `else` block is an `if` statement. + +* There has to be a space between many operators and character literals, +as the parser may treat the apostrophe as a part of the operator. + +## Preprocessor + +* The preprocessor cannot expand symbols to something more complex than an identifier or a literal. + +* The preprocessor cannot define symbols for use in other files. + +* The closest to C's `#define` is `#use`. +Unlike `#define`, such definitions are not visible within the preprocessor. + +* Directives like `#use` and `#pragma` affect the entire file, not just the code after the directive. + +* The preprocessor cannot include files. + +## Semantics + +* There is no automatic integer promotion. An operation of two bytes will yield a byte. For example: + + byte a + byte b + word w + w = a * b + + may yield unexpected results. To prevent this, cast at least one argument: + + w = word(a) * b + + This issue applies mostly to the `*` and `<<` operators. + +* There is no padding in structs. + + + + + diff --git a/docs/various/faq.md b/docs/various/faq.md index c6bfb9f6..27e38565 100644 --- a/docs/various/faq.md +++ b/docs/various/faq.md @@ -83,6 +83,10 @@ or generate a static binary and link it manually using the `file` directive. Since the compiler is a work-in-progress, some of the mentioned issues might be improved upon in the future. +### I have experience with C and/or assembly. What should I keep in mind? + +See the [differences from C](./cdiff.md) and the [differences from assembly](./asmdiff.md). + ### Why is it called Millfork? It stands for **MI**ddle **L**evel **L**anguage **FOR** **K**ommodore computers. diff --git a/docs/various/optimization.md b/docs/various/optimization.md new file mode 100644 index 00000000..15e8fa94 --- /dev/null +++ b/docs/various/optimization.md @@ -0,0 +1,103 @@ +[< back to index](../doc_index.md) + +# Optimization guidelines + +## Command-line options + +* The default options provide literally no optimizations. +Consider using at least `-O1` for quick compilation and `-O4` for release builds. + +* Inlining can drastically improve performance. Add `-finline` to the command line. + +* If you're not using self-modifying code or code generation, +enabling interprocedural optimizations (`-fipo`) and stdlib optimizations (`-foptimize-stdlib`) can also help. + +* For convenience, all options useful for debug builds can be enabled with `-Xd`, +and for release builds with `-Xr`. + +* 6502 only: If you are sure the target will have a CPU that supports so-called illegal/undocumented 6502 instructions, +consider adding the `-fillegals` option. Good examples of such targets are NES and C64. + +## Alignment + +* Consider adding `align(fast)` or even `align(256)` to arrays which you want to access quickly. + +* 6502 only: Consider adding `align(fast)` to the hottest functions. + +* If you have an array of structs, consider adding `align(X)` to the definition of the struct, +where `X` is a power of two. Even if this makes the struct 12 bytes instead of 11, it can still improve performance. + +## Variables + +* Use the smallest type you need. Note that Millfork supports integers of any size from 1 to 16 bytes. + +* Consider using multiple arrays instead of arrays of structs. + +* Avoid reusing temporary variables. +It makes it easier for the optimizer to eliminate the variable entirely. + +## Functions + +* Write many functions with no parameters and use `-finline`. +This will simplify the job for the optimizer and increase the chances of certain powerful optimizations to apply. + +* Avoid passing many parameters to functions. +Try to minimize the number of bytes passed as parameters and returned as return values. + +## Loops + +* For `for` loops that use a byte-sized variable and whose body does not involve function calls or further loops, +use a unique iteration variable. Such variable will have a bigger chance of being stored in a CPU register. +For example: + + byte i + byte j + for i,0,until,30 { .... } + for j,0,until,40 { .... } + + is usually better than: + + byte i + for i,0,until,30 { .... } + for i,0,until,40 { .... } + +* 8080/Z80 only: The previous tip applies also for loops using word-sized variables. + +* When the iteration order is not important, use `paralleluntil` or `parallelto`. +The compiler will try to choose the optimal iteration order. + +* Since 0.3.18: When the iteration order is not important, +use `for ix,ptr:array` to iterate over arrays of structs. + +* 6502 only: When iterating over an array larger than 256 bytes, whose element count is a composite number, +consider splitting it into less-than-256-byte sized slices and use them within the same iteration. +For example, instead of: + + word i + for i,0,paralleluntil,1000 { + screen[i] = ' 'scr + } + + consider: + + byte i + for i,0,paralleluntil,250 { + screen[i+000] = ' 'scr + screen[i+250] = ' 'scr + screen[i+500] = ' 'scr + screen[i+750] = ' 'scr + } + + Note that the compiler might do this optimization automatically + for simpler loops with certain iteration ranges, but it is not guaranteed. + +# Arithmetic + +* Avoid 16-bit arithmetic. Try to keep calculations 8-bit for as long as you can. +If you can calculate the upper and lower byte of a 16-bit value separately, it's usually better to do so. + +* Avoid arithmetic larger than 16-bit. + +* Use `nonet` if you are sure that the result of shifting will fit into 9 bits. +Use `nonet` when doing byte addition that you want to promote to a word. +