MOSpeed

From C64-Wiki
Jump to navigationJump to search
MOSpeed
Console output
Developer EgonOlsen71
Company
Publisher
Release 2018
Licence Open Source / The Unlicense
Platform PC, C64
Genre BASIC Cross Compiler
Operation Keyboard
Media Download
Language(s) Language:english
Information

MOSpeed is an optimizing BASIC Cross Compiler by EgonOlsen71. It's Open Source/Public Domain. Being a cross compiler, it runs on a PC but generates 6502-Machine language for the Commodore 64, for the VIC 20, for the x16 (the Commander (NOT the Commodore) X16), Javascript,Python or Powershell scripts.

Description[edit | edit source]

MOSpeed is an optimizing BASIC Cross Compiler that's controlled on a PC via the command line/shell. It expects a BASIC V2 program in text file format, just like C64 Studio or CBM prg Studio are using it. It supports the usual placeholders like {clear}. In addition, it can operate directly on prg files and converts them to text format before compiling them. In this case, the file to compile has to end on .prg or the compiler won't recognize it as a prg file. MOSpeed is written in Java. It requires a Java runtime environment version 11 or higher.

MOSpeed can be configured via command line parameters. It doesn't make use of compiler directives directly in the source code like some other compilers for the C64 do.

MOSpeed in an optimizing compiler. It's able to detect constants (even if those are declared as Variables) and replaces them accordingly. Unused variables or the calculations used to set them will be detected and removed as well.

The performance on average is between 10 and 20 % higher than the performance of a BASIC-BOSS compiled program without any special variable declarations, but can be much higher in some special cases as well. On average, MOSpeed increases the performance of BASIC V2 programs by a factor of 3 to 5.

Loading, startup, controls[edit | edit source]

MOSpeed is a command line application for the PC. Because MOSpeed is written in Java, the compiler is distributed as a JAR-file. This distribution contains two script file - one for Windows (mospeed.cmd) and one for Linux (mospeed.sh) - which can be used to start the compiler.

The compiler needs at least the name/path of the source file to run or /? to display the integrated help text. Started with just the source file name, the compiler will create (assuming that the compile process went fine) a prg-file named like the source file but with two plus signs as prefix. For example: example.bas becomes ++example.prg

The compilation process can be configured by using various command line parameters.

The source code can use line numbers (as usual) but also labels, which the interpreter doesn't support. MOSpeed translates these label to line numbers at compile time.

In addition to the command line compiler, there's a simple GUI available as well. It allows for selecting the target platform and the file to compile but always compiles using the default settings. It can be started by using the mospeed_gui.xxx script files in the dist directory (.cmd for Windows, .sh for Linux).

Command line parameters[edit | edit source]

These command line parameter can be appended directly to the call to start the compiler. Order doesn't matter.
All parameters start with a /, alternatively a -sign can be used.
In the following, true|false means that the feature can be enabled (true) or disabled (false).

  • /target=<target file>: Sets the name of the target file. If nothing is given, the name will be generated from the source file's name (as described above). It's possible to use a complete path name.
  • /platform=xxxx: Sets the target platform. Supported are c64 for C64-compatible machine language, vic20 or vc20 for vic20-compatible machine language, x16 for x16-compatible machine language, js for Javascript, ps for Powershell and py for Python. Default is c64.
  • /generatesrc=true|false: If true, the generated source codes for the assembly like intermediate language as well as for the 6502-Assembly will be written to disk as well. Default is false.
  • /constfolding=true|false: If true, constants will be detected and replaced by their actual values. Examples are the often used addresses for VIC or SID. Default is true.
  • /constprop=true|false: If true, constant propagation will be used. I.e. constants won't only just detected directly, but also if calculations result in constant values. Default is true.
  • /ilangopt=true|false: If true, the generated intermediate code will be optimized. This increases the speed and usually reduces the size. Default is true.
  • /nlangopt=true|false: If true, the generated 6502-Assembly code will be optimized. This increases the speed and usually reduces the size. This optimization can take some time, depending on the size of the program. Default is true.
  • /smartlinker=true|false: If true, only used parts of the runtime will be linked to the actual program. If false, the complete runtime will be linked. Default is true.
  • /deadstoreopt=true|false: If true, dead, i.e. unused variable assigments will be detected and removed. In most cases, this is just fine but in some cases (for example when calling but not using the result of RND) it can cause the results of the compiled program to differ from the interpreted ones. This parameter affects integer and floating point variables only. Default is true.
  • /deadstoreoptstr=true|false: Just like /deadstoreopt, but for string variables. Default is true.
  • /deadcodeopt=true|false: enables/disables dead code elimination. Default is false.
  • /loopopt=true|false: Detects and removes empty loops. These loops don't have to be empty in the source code, but if the values that are calculated inside them aren't actually used, they might get removed anyway. Default is true.
  • /addressheader=true|false: enables/disables the writing of the two address header bytes into the generated PRG file. Default is true.
  • /floatopt=true|false: If true, various optimizations for floating point calculations will be used. In some case, caused by inaccuracies in the calculations, this can lead to small differences but in most cases, this shouldn't be a problem if it's even noticable. Default is false for compatibility reasons.
  • /xfloatopt=true|false: enables/disables additional floating point optimizations. While they speed up some operations by up to 25%, they need more memory. Default is true.
  • /intopt=true|false: If true, certain integer operations will be optimized. In general MOSpeed treats integer variables like the interpreter does for compatibility reasons. By using this parameter, this restriction can me loosened somewhat. Default is true.
  • /compactlevel=[0|3...]: If >= 3, the generated code will be optimized for size. This has an impact on performance, but not in the same proportion as the size reduction. The higher the value, the less of a performance impact there will be, but the size reduction might be smaller as well. 4 or 5 are usually a good compromise, which often leads to a size reduction of 10 to 15 % with a performance impact of 0.5- to 1 %. Default is 0, which disables this optimization.
  • /progstart=xxxxx|$yyyy: Sets the start address of the compiled program. This value can be either decimal or hexadecimal. If it's close to the machine's BASIC start address, a BASIC header to run the program will be added automatically, otherwise it won't. Default is 2072.
  • /varstart=xxxxx|$yyyy: Sets the start address of the variable/constants memory. This value can be either decimal or hexadecimal. If nothing is given, this memory will follow the memory used for the runtime. This is also the default.
  • /varend=xxxxx|$yyyy: Sets the end of the variable memory. This value can be either decimal or hexadecimal. Because the actual variable memory doesn't change at runtime, this actually sets the end of the string's heap memory. Make sure that the memory is readable, i.e. that it isn't shadowed by the ROM. The default is determined by the value configured in address 55/56.
  • /runtimestart=xxxxx|$yyyy: Sets the start address of the runtime code. This value can be either decimal or hexadecimal. If nothing is given, it will follow the actual program's memory. This is also the default.
  • /sysbuffer=xxxxx|$yyyy: Sets the start address of the buffer used to execute SYS commands with parameters. Default is 820 for the CBM machines and 1024 for the X16.
  • /alloff=true|false: If true, all optimizations which are enabled by default will be turned off. Default is false.
  • /memconfig=0|3|8: The memory configuration in case of a VIC20 as the target machine. 0 means 'unexpanded', 3 means with a 3k expansion, 8 means with an 8k or larger expansion. Default is 8.
  • /vice=<path>: This sets the path to an instance of the VICE-Emulator (or some other with similar behaviour). After a successful compilation, the compiled program will be started in that emulator. By default this doesn't happen.
  • /symboltable=<file>: This only applies to the X16 target platform. It lets you specify a different symbol table to compile for different ROM releases. The default symbol table should match the latest ROM release.
  • /nondecimals=true|false: if true, hexadecimal and binary numbers can be indicated by & and %. Default is false, except for the X16 platform, where it's enabled by default.
  • /tolower=true|false: if true, all strings in the source code will be treated as lower case. This can be useful when compiling BASIC code copied directly from an emulator where all strings are given in upper case. Default is false.
  • /flipcase=true|false: if true, the casing of strings in the source code will be reversed. This can be useful when compiling BASIC code copied directly from an emulator. Default is false.
  • /multipart=true|false: if false (default) the target file contains all binary data regardless of the address in memory. If true, several files will be written if the addresses of the program's parts aren't adjacent.
  • /memhole=<start1-end1>,<start2-end2>,...: Defines holes/locked regions in memory. The compiled program won't use these memory locations for compiled code and variables. If a hole is located after the end of the compiled program, it will be ignored. Default is none.
  • /boost=true|false: if true, a compiled C64 program will use the C128's 2 Mhz mode to increase performance up to 25%. This only works on the C128 in C64 mode, it has no effect when run on a real C64. It might also not be compatible with all programs. Default is false.
  • /bigram=true|false: - If true, the RAM under the C64's BASIC ROM as well as the higher 4K of RAM will be used for the compiled program as well. This will reduce performance, especially when accessing memory under the ROM. Default is false.
  • /compression=true|false: - If true, the compiled program will be compressed to achieve a smaller file size. The compressed file will be saved in addition to the normal binary. Compression isn't always possible. In that case, no compressed file will be written.
  • /inlineasm=true|false: - If true, inline assembly code can be used, marked by REM [...;...;...]. Default is false.
  • /deploy=<ip/domain> - If set to an ip or domain on which an Ultimate64 is listening, the program will be transfered and started on that device. Default is empty.
  • /printopt=true|false - *Experimental* - If true, the compiler tries to rearrange texts in PRINT statements to save memory at the expense of speed. Default is false.
  • /arrayopt=true|false - *Experimental* - If true, the compiler tries to optimize access speed of multi-dimensional arrays at the expense of memory usage. Default is false.
  • /assignmentopt=true|false - *Experimental* - If true, the compiler tries to optimize assignments. Default is false.
  • /varopt=true|false - *Experimental* - If true, the compiler tries to move integer variables into the zeropage if possible and applicable. Default is false.


Example:

mospeed example.bas -target=example -platform=vic20

Changes compared to BASIC 2.0[edit | edit source]

MOSpeed tries to stay compatible with the BASIC V2 interpreter when possible. However, there are some deviations.

  • LIST: Will be ignored.
  • RUN: Any line number after RUN will be ignored.
  • NEW, STOP: Will be treated like END.
  • DIM with variables: While this actually isn't supported, it might work to a degree if MOSpeed can detect these variables as constants.
  • Limited support for USR: This function is supported, but only a single floating point value is allowed as input. Strings or multiple parameters are not allowed.
  • SYS with parameters: This option is supported, but because it's actually an interpreter hack in the first place, the call itself might be slower in the compiled code.
  • INPUT with double quotes: When using INPUT MOSpeed is a little more forgiving with double quotes.
  • Unified rounding: The interpreter chooses different approaches to rounding in different contexts. The rounding differs inside of functions, for example. An example: A=145/3*3 assigns 145 to A, but PEEK(145/3*3) reads from 144, not 145. MOSpeed unifies this behaviour, so that in this example, the outcome will always be 145 (just like in the BASIC 7.0 of the C128).
  • floating point accuracy: The results of floating point operations may show slight differences between the interpreter and MOSpeed, depending on the applied optimizations. In almost all cases, this shouldn't be a problem at all. In addition, MOSpeed tries to workaround a bug in the interpreter ROM, so that the results might turn out to be more precise.
  • IF with Strings and without a comparison operator: BASIC V2 will print out OK for A$="":B$="YEAH":IFA$THEN?"OK", even though A$ is empty. The reason is that it's not using the variable inside the IF (A$ in this case), but the last assigned one, in this case B$. MOSpeed always tests against the variables given after the IF.


Runtime errors[edit | edit source]

Runtime errors in the program (for example divisions by zero so similar) will be catched and printed out in most cases, but the Line number will always be 0.

Target platforms[edit | edit source]

C64 / 6502 machine code[edit | edit source]

For this (default-)target platform, the compiler creates a.prg-file, which can be executed directly in an emulator. It can be transfered to a real C64 by using the usual means and executed there as well.

VC/VIC20 / 6502 machine code[edit | edit source]

The compiler creates a .prg-file, which can be executed directly in an emulator. It can be transfered to a real VIC20 by using the usual means and executed there as well.

X16 / 6502 machine code[edit | edit source]

The compiler creates a .prg-file, which can be executed directly in an emulator. It can be transfered to a real x16 if it's finally available.

Javascript[edit | edit source]

For this target platform, the compiler creates two files, one .js, which contains the the compiled Javascript and the Javascript runtime for it as well as a .html-File, which includes the required resources and starts the compiled program. There are multiple options to start it. By default, the program will output into the Javascript console and reads input (INPUT as well as GET) via Javascript prompts. The program runs inside the starter's thread, usually the browser's main thread, which in turn will block this thread until the program terminates.

As an alternative to that, the program can be run in a WebWorker. That leaves the starter's thread free to execute other tasks.

A third option allows for the output to go into a browser console view similar to the C64 BASIC editor. GET inputs will be read from there as well, but INPUT is still handled by Javascript prompts for technical reasons.

To select an option, one has to modify the .html-file accordingly. The corresponding sections are marked with comments. For using the browser console output, the files console.js and CommodoreServer.ttf have to copied over from the project's resource directory into the .html-file's directory.

Powershell[edit | edit source]

The compiler creates a .ps1-file that can be run in a Powershell console.

Python[edit | edit source]

The compiler creates a .py-file that can be run in a console.


Inline assembly support[edit | edit source]

MOSpeed supports inline assembly code, but it's disabled for compatibility reasons by default. You can enable it by setting /inlineasm=true. When enabled, everything behind a REM in square brackets will be processed by the built-in assembler. Multiple commands/labels/assignments can be written in one line separated by a semicolon. For example:

10 print "hello asm"
20 rem [ ldx #$ff ]
30 rem [ loopy; inc 53280 ]
40 rem [ dex; bne loopy ]
50 goto 10


This will print "hello asm" in an endless loop but will also make the border flash by using machine language code. You can define labels as well as constants in the assembly code. So something like this will work as well:


10 print "hello asm" 20 rem [ numby=$ff; ldx #numby ] 30 rem [ loopy; inc 53280 ] 40 rem [ dex; bne loopy ] 50 goto 10


You can access BASIC variables and lines by adding ! as a postfix, like so:


10 print "hello asm": i%=255 20 rem [ ldx i%! ] 30 rem [ inc 53280 ] 40 rem [ dex; bne 30! ] 50 goto 10


You can also do something like this:


10 print "hello asm": i%=255 20 rem [ ldx numby ] 30 rem [ inc 53280 ] 40 rem [ dex; bne 30!; jmp 50! ] 45 rem [ numby; .byte 255 ] 50 goto 10


So, basically, you can do everything that you can do in the actual assembler that comes with this project. That includes screwing up the compiled program and/or the runtime by using this option if you don't know, what you are doing. Even evil hacks like this are possible:


10 print "hello asm" 20 rem [ldx #$ff] 30 rem [jmp 40!; *=$2000;] 40 rem [inc 53280] 50 rem [dex; bne 40!] 60 goto 10


With this, you are splitting compiled code into one part starting at $0801 (by default) and everything after line 30 continuing at $2000. While this works, I suggest not to do things like this. Use the memhole-option instead...

Integration with CBM prg Studio[edit | edit source]

MOSpeed can be integrated with CBM prg Studio, so that the compiler runs on every build and the result will be executed in the emulator. This integration isn't perfect, but quite helpful anyway. To make this a little clearer, here's an example.

In this example, I'm using a project named "compilertest" and a source file named "fractal.bas"

project structure.png


Now select the menu option "Build" -> "Build Events...":

build events.png



Check the "Post-Build Commands" box and enter the following into the now active text area:

[DOSCMD]cd <Path to MOSpeed> && <Drive, on which MOSspeed is located>: && mospeed.cmd [ProjectDir]<Name of the source file> -vice=<path to VICE>\x64.exe

in this particular example, it would look like this:

[DOSCMD]cd e:\src\myprojects\workspace\BasicV2\dist && e: && mospeed.cmd [ProjectDir]fractal.bas -vice=e:\vice\WinVICE-2.2-x64\x64.exe

events dialog.png


The command is a concatenated list of DOS commands. According to the documention, this shouldn't actually be needed, but I couldn't get it to work otherwise. This command will be executed after each build (for example when pressing F6 in CBM prg Studio). Please note that it doesn't use the .prg-file that CBM prg Studio creates when building, but the actual file. Sadly, CBM prg Studio offers no option so far to access the generated code right before the creation of the PRG. Because of this, this solution isn't ideal. The name of the source file has to be given directly in the command (fractal.bas in this example). CBM prg Studio offers a [prg]-placeholder, but that references the built PRG-file, which is of no use for MOSPeed. Another problem is, that CBM prg Studio only shows the compiler's output in case of a problem. So you don't see what's going on and have to wait for the compiler to complete its job, especially on larger programs.

A bug in CBM prg Studio causes the &&-characters in the command to be converted into &amp;&amp; on restart. You have to correct them each time you are starting the IDE.

Links[edit | edit source]

Program