├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── .gitmodules ├── COPYING ├── Makefile.am ├── README.md ├── TODO ├── beetle.magic ├── bootstrap ├── bootstrap.conf ├── build-aux └── .gitignore ├── configure.ac ├── doc ├── .dir-locals.el ├── .gitignore ├── Makefile.am ├── beetle.pdf ├── beetle.tex ├── cbeetle.pdf ├── cbeetle.tex.in ├── latexmkrc ├── papers │ ├── intro.pdf │ ├── intro.tex │ ├── litencode.pdf │ ├── litencode.tex │ ├── tradeoffs.pdf │ └── tradeoffs.tex ├── primes.c ├── shell.pdf └── shell.tex.in ├── m4 ├── .gitignore ├── ax_cc_maxopt.m4 ├── ax_check_compile_flag.m4 ├── ax_compiler_vendor.m4 ├── ax_gcc_archflag.m4 └── ax_gcc_x86_cpuid.m4 ├── src ├── .gitignore ├── ARMbeetle.bas ├── Makefile.am ├── beetle-dump ├── beetle-dump.1 ├── beetle.h ├── beetle_aux.h ├── beetle_debug.h ├── beetle_opcodes.h ├── beetlei.1 ├── beetlei.in ├── completions.h ├── debug.c ├── external_syms.h ├── loadobj.c ├── main.c ├── private.h ├── stringify.h ├── tbl_commands.h ├── tbl_opts.h.in ├── tbl_registers.h └── vm.c └── tests ├── .gitignore ├── Makefile.am ├── arithmetic.c ├── badobj1 ├── badobj2 ├── badobj3 ├── badobj4 ├── branch.c ├── comparison.c ├── doloop.c ├── exceptions.c ├── hello.correct ├── hello.txt ├── init.c ├── lib.c ├── link.c ├── literals.c ├── load_object.c ├── logic.c ├── memory.c ├── registers.c ├── run-test ├── run.c ├── single_step.c ├── stack.c ├── testobj1 ├── testobj2 ├── testobj3 └── tests.h /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macos-latest] 10 | include: 11 | - os: ubuntu-latest 12 | shell: bash 13 | - os: macos-latest 14 | shell: bash 15 | - os: windows-latest 16 | sys: mingw64 17 | arch: x86_64 18 | shell: msys2 19 | - os: windows-latest 20 | sys: mingw32 21 | arch: i686 22 | shell: msys2 23 | runs-on: ${{ matrix.os }} 24 | defaults: 25 | run: 26 | shell: ${{ matrix.shell }} {0} 27 | steps: 28 | - uses: msys2/setup-msys2@v2 29 | if: ${{ matrix.os == 'windows-latest' }} 30 | with: 31 | release: false 32 | msystem: ${{matrix.sys}} 33 | install: >- 34 | patch git groff help2man 35 | mingw-w64-${{matrix.arch}}-autotools 36 | mingw-w64-${{matrix.arch}}-gcc 37 | - uses: actions/checkout@v3 38 | with: 39 | submodules: true 40 | fetch-depth: 0 41 | - name: Install dependencies (Ubuntu) 42 | if: ${{ matrix.os == 'ubuntu-latest' }} 43 | run: sudo apt-get -y install texlive-latex-extra texlive-science texlive-fonts-recommended texlive-fonts-extra help2man latexmk hevea valgrind 44 | - name: Install dependencies (macOS) 45 | if: ${{ matrix.os == 'macos-latest' }} 46 | run: | 47 | brew install help2man automake libtool 48 | # Prepend optional brew binary directories to PATH 49 | echo "$(brew --prefix m4)/bin" >> $GITHUB_PATH 50 | - name: Set up environment (Ubuntu) 51 | if: ${{ matrix.os == 'ubuntu-latest' }} 52 | run: | 53 | echo "ASAN=yes" >> $GITHUB_ENV 54 | echo "LSAN_OPTIONS=verbosity=1:log_threads=1" >> $GITHUB_ENV 55 | - name: Set up environment (Windows) 56 | if: ${{ matrix.os == 'windows-latest' }} 57 | run: | 58 | # Define _POSIX to get a full set of POSIX signal names from signal.h on mingw 59 | echo "CPPFLAGS=-D_POSIX" >> $GITHUB_ENV 60 | - name: Build 61 | run: | 62 | ./bootstrap 63 | if [[ "$ASAN" == "yes" ]]; then ./configure --enable-silent-rules CFLAGS="-g3 -fsanitize=address -fsanitize=undefined" LDFLAGS="-fsanitize=address -fsanitize=undefined"; else ./configure --enable-silent-rules; fi 64 | make check 65 | make distcheck 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *~ 3 | /beetle*.tar.gz 4 | .gdb_history 5 | /.vscode 6 | Makefile 7 | Makefile.in 8 | TAGS 9 | /dissertation 10 | /INSTALL 11 | /README 12 | /aclocal.m4 13 | /ar-lib 14 | /autom4te.cache 15 | /compile 16 | /config.h 17 | /config.h.in 18 | /config.log 19 | /config.status 20 | /configure 21 | /install-sh 22 | /libtool 23 | /missing 24 | /depcomp 25 | /lib 26 | /stamp-h1 27 | /test-driver 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gnulib"] 2 | path = gnulib 3 | url = git://git.sv.gnu.org/gnulib.git 4 | [submodule "doc/bibtex"] 5 | path = doc/bibtex 6 | url = https://github.com/rrthomas/bibtex.git 7 | [submodule "gl-mod/bootstrap"] 8 | path = gl-mod/bootstrap 9 | url = https://github.com/gnulib-modules/bootstrap.git 10 | [submodule "mijit-beetle"] 11 | path = mijit-beetle 12 | url = https://github.com/rrthomas/mijit-beetle 13 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # Top-level Makefile.am 2 | # 3 | # (c) Reuben Thomas 2011-2024 4 | # 5 | # The package is distributed under the GNU General Public License version 3, 6 | # or, at your option, any later version. 7 | # 8 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | # RISK. 10 | 11 | SUBDIRS = lib src tests doc 12 | 13 | ACLOCAL_AMFLAGS = -I m4 14 | 15 | EXTRA_DIST = m4/gnulib-cache.m4 16 | 17 | release: distcheck 18 | git diff --exit-code && \ 19 | git tag -a -m "Release tag" "v$(VERSION)" && \ 20 | git push && git push --tags && \ 21 | woger github \ 22 | package=$(PACKAGE) \ 23 | version=$(VERSION) \ 24 | dist_type=tar.gz 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beetle 2 | 3 | by Reuben Thomas 4 | https://github.com/rrthomas/beetle 5 | 6 | Beetle is a simple virtual machine designed for the Forth language. It uses 7 | a byte-stream code designed for efficient execution which is binary portable 8 | between implementations. It has been implemented in C (for POSIX systems) 9 | and hand-optimised assembler (for ARM). The C implementation should run on 10 | any POSIX system; the assembler version runs pForth (see below) at up to 11 | half the speed of the corresponding native code compiler and generates more 12 | compact code. Beetle is designed to be embedded in other programs; a simple 13 | shell has been written to demonstrate this ability. In the C implementation, 14 | all memory references are bounds checked. An I/O library is implemented; 15 | access to native code routines is also possible, allowing Beetle and C 16 | programs to call each other. 17 | 18 | This package comprises the definition of the Beetle virtual machine and an 19 | implementation in ISO C99 using POSIX APIs. Detailed documentation is in the 20 | `doc` directory; installation instructions follow. 21 | 22 | The package is distributed under the GNU General Public License version 3, 23 | or, at your option, any later version. 24 | 25 | THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 26 | RISK. 27 | 28 | 29 | ## Installation and compatibility 30 | 31 | Beetle should work on any POSIX-1.2001-compatible system. Beetle has been 32 | tested on x86_64 GNU/Linux with GNU C. 33 | 34 | Previous releases were known to work on Acorn RISC OS 3, Digital UNIX 35 | V3.2, UNIX System V Release 4.0, ULTRIX 4.3, NetBSD 1.2, MSDOS 6, and 36 | Atari TOS 1.4. 37 | 38 | Reports on compatibility, whether positive or negative, are welcomed. 39 | 40 | 41 | ### Building from a release tarball 42 | 43 | Perl and help2man are required to build from source. For building from git, 44 | see below. 45 | 46 | To build Beetle from a release tarball, run 47 | 48 | `./configure && make && make check` 49 | 50 | For the bibliographies in the documentation to be built correctly, GNU Make 51 | should be used. 52 | 53 | 54 | ### Building Beetle from git 55 | 56 | The GNU autotools are required: automake, autoconf and libtool. 57 | [Gnulib](https://www.gnu.org/software/gnulib/) is also used, with a 58 | third-party `bootstrap` module; these are installed automatically. 59 | 60 | To build from a Git repository, first run 61 | 62 | ``` 63 | ./bootstrap 64 | ``` 65 | 66 | Then see "Building from source" above. 67 | 68 | To build the PDF documentation, a comprehensive TeX system such as TeXLive 69 | is required. This is only necessary when building from Git, as pre-built 70 | PDFs are supplied in release archives. 71 | 72 | 73 | ## Use 74 | 75 | Run `beetle` (see `beetle --help` and `shell.pdf` for documentation). If 76 | you have `rlwrap`, you can run `beetlei` instead to get readline support. 77 | 78 | 79 | ### Demo: Hello, world! 80 | 81 | In `tests/hello.txt` is a command file for the shell that demonstrates its 82 | use as a crude assembler. Run the following commands to see it in action: 83 | 84 | ``` 85 | cd tests 86 | beetle < ./hello.txt 87 | beetle hello.obj 88 | ``` 89 | 90 | 91 | ## Documentation 92 | 93 | The canonical documentation consists of: 94 | 95 | * _[The Beetle Forth Virtual Machine](doc/beetle.pdf)_ 96 | The design of the Beetle Forth virtual machine is described. Essential 97 | reading for those programming or implementing the VM. 98 | * _[An implementation of the Beetle virtual machine for POSIX](doc/cbeetle.pdf)_ 99 | A portable implementation of Beetle is described, with instructions for 100 | porting, compiling and running it. 101 | * _[A simple shell for the Beetle virtual machine](doc/shell.pdf)_ 102 | The user guide for Beetle’s shell. 103 | 104 | The following documents contain extra material on Beetle’s design, but many 105 | details are out of date: 106 | 107 | * _[An Introduction to the Beetle Forth Virtual Processor](doc/papers/intro.pdf)_ 108 | An introduction to the system; this is the best paper to read first. It was 109 | published in ACM SIGPLAN Notices February 1997. 110 | * _[Beetle and pForth: a Forth virtual machine and compiler](https://rrt.sc3d.org/Software/beetle/dissertation/report/badiss.pdf)_ 111 | I developed Beetle for my BA dissertation project. _(I used to refer to it 112 | as a “virtual processor”; I now use the now-standard term “virtual 113 | machine”.)_ My BA dissertation contains older versions of all the papers 114 | mentioned above, as well as a description of the project that produced them. 115 | * _[Tradeoffs in the implementation of the Beetle virtual machine](doc/papers/tradeoffs.pdf)_ 116 | A hand-coded implementation of Beetle is described, and compared to the C 117 | version. 118 | * _[Encoding literals in a portable byte-stream interpreter](doc/papers/litencode.pdf)_ 119 | Various methods of encoding literal numbers in a byte stream are compared. 120 | 121 | 122 | ## pForth 123 | 124 | [pForth](https://github.com/rrthomas/pforth) is an ANSI Forth compiler that 125 | targets Beetle. 126 | 127 | 128 | ## Running Beetle object files 129 | 130 | The C implementation of Beetle allows a hash-bang line to be prepended to an object file, so that they can be run directly. A suggested line is: 131 | 132 | ``` 133 | #!/usr/bin/env beetle 134 | ``` 135 | 136 | A magic file for the file(1) command is also provided: beetle.magic. 137 | This file should be part of file >= 5.33. 138 | 139 | 140 | ## Hand-written ARM assembler version 141 | 142 | `ARMbeetle.bas` contains a hand-written ARM assembler version of Beetle, 143 | written in the BBC BASIC assembler (for RISC OS). 144 | 145 | 146 | ## Bugs and comments 147 | 148 | Please send bug reports (preferably as [GitHub issues](https://github.com/rrthomas/beetle/issues)) 149 | and comments. I’m especially interested to know of portability bugs. 150 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Reorganize C implementation documentation into user’s guide and programmer’s 2 | guide. 3 | 4 | Turn tests into debugger scripts. 5 | 6 | Compile at load time using ideas in DDJ article 7 | (~/notmine/Computer/Language/forth/LoadCompilation). 8 | -------------------------------------------------------------------------------- /beetle.magic: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # $File: beetle,v 1.2 2018/02/05 23:42:17 rrt Exp $ 3 | # beetle: file(1) magic for Beetle VM object files 4 | # https://github.com/rrthomas/beetle/ 5 | 6 | # Beetle object module 7 | 0 search/64 BEETLE\000 Beetle VM object file 8 | !:strength + 50 9 | -------------------------------------------------------------------------------- /bootstrap.conf: -------------------------------------------------------------------------------- 1 | # bootstrap.conf (beetle) version 2023-01-05 2 | 3 | # This file is part of beetle. 4 | # See COPYING for license. 5 | 6 | ## -------------- ## 7 | ## Configuration. ## 8 | ## -------------- ## 9 | 10 | # File that should exist relative to the top directory of a checked out 11 | # hierarchy, but not in a distribution tarball. 12 | checkout_only_file=.gitignore 13 | 14 | # List of programs, minimum versions, and software urls required to 15 | # bootstrap, maintain and release. 16 | 17 | # Build prerequisites 18 | buildreq=' 19 | git 1.5.5 https://git-scm.com 20 | m4 1.4.12 https://www.gnu.org/s/m4 21 | perl 5.5 https://perl.com 22 | tar - https://www.gnu.org/s/tar 23 | ' 24 | 25 | # Non-default gnulib directory options. 26 | local_gl_path=gl-mod/bootstrap 27 | gnulib_git_submodules=' 28 | mijit-beetle 29 | gl-mod/bootstrap 30 | doc/bibtex 31 | ' 32 | 33 | # Additional gnulib-tool options to use. 34 | gnulib_tool_options=' 35 | ' 36 | 37 | # gnulib modules used by this package. 38 | gnulib_modules=' 39 | binary-io 40 | bootstrap 41 | dirname 42 | fcntl 43 | fdatasync 44 | getline 45 | getopt-gnu 46 | glob 47 | largefile 48 | manywarnings 49 | minmax 50 | progname 51 | stdlib 52 | sys_wait 53 | ssize_t 54 | sys_stat 55 | sys_types 56 | unistd 57 | valgrind-tests 58 | verify 59 | xvasprintf 60 | ' 61 | 62 | ## --------------- ## 63 | ## Hook functions. ## 64 | ## --------------- ## 65 | 66 | # beetle_ignore_gnulib_ignore 67 | # ---------------------------- 68 | # gnulib-tool updates m4/.gitignore and lib/.gitignore, and keeping 69 | # generated files under version control does not make sense. Since 70 | # lib is entirely ignored, we only need to prepopulate the m4 ignore 71 | # files with generated files not tracked by gnulib-tool. 72 | beetle_ignore_gnulib_ignore () 73 | { 74 | $debug_cmd 75 | 76 | $require_macro_dir 77 | 78 | if test -f "$macro_dir/.gitignore" ; then 79 | : 80 | else 81 | func_verbose "creating initial \`$macro_dir/.gitignore'" 82 | cat > $macro_dir/.gitignore <<\EOF 83 | # files created by bootstrap, but that gnulib doesn't track 84 | *~ 85 | /.gitignore 86 | /gnulib-comp.m4 87 | EOF 88 | fi 89 | } 90 | func_add_hook func_prep beetle_ignore_gnulib_ignore 91 | 92 | 93 | # Local variables: 94 | # mode: shell-script 95 | # sh-indentation: 2 96 | # eval: (add-hook 'write-file-functions 'time-stamp) 97 | # time-stamp-start: "# bootstrap.conf (beetle) version " 98 | # time-stamp-format: "%:y-%02m-%02d" 99 | # time-stamp-end: "$" 100 | # End: 101 | -------------------------------------------------------------------------------- /build-aux/.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap.in 2 | /extract-trace 3 | /funclib.sh 4 | /inline-source 5 | /options-parser 6 | /ar-lib 7 | /compile 8 | /config.guess 9 | /config.sub 10 | /depcomp 11 | /install-sh 12 | /ltmain.sh 13 | /mdate-sh 14 | /missing 15 | /snippet/ 16 | /test-driver 17 | /texinfo.tex 18 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # configure.ac for Beetle 2 | # Process this file with autoconf to produce a configure script 3 | # 4 | # (c) Reuben Thomas 2011-2023 5 | # 6 | # The package is distributed under the GNU General Public License version 3, 7 | # or, at your option, any later version. 8 | # 9 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | # RISK. 11 | 12 | AC_PREREQ([2.71]) 13 | AC_INIT([Beetle],[3.0.2],[rrt@sc3d.org]) 14 | AC_CONFIG_AUX_DIR([build-aux]) 15 | AM_INIT_AUTOMAKE([-Wall -Werror foreign]) 16 | 17 | # Check for programs 18 | AC_PROG_CC 19 | gl_EARLY 20 | AM_PROG_AR 21 | LT_INIT 22 | AC_PATH_PROG(LATEXMK, latexmk, true) 23 | AM_CONDITIONAL([HAVE_LATEXMK], [test "$ac_cv_path_LATEXMK" != "true"]) 24 | 25 | # help2man 26 | # Set a value even if not found, so that an invocation via build-aux/missing works 27 | AC_PATH_PROG([HELP2MAN], [help2man], [help2man]) 28 | 29 | # Optimization 30 | AX_CC_MAXOPT 31 | 32 | # Readline wrapper 33 | AC_PATH_PROG(RLWRAP, rlwrap) 34 | AM_CONDITIONAL([HAVE_RLWRAP], [test -n "$ac_cv_path_RLWRAP"]) 35 | 36 | # beetle-mijit 37 | AC_ARG_WITH([mijit], 38 | [AS_HELP_STRING([--with-mijit], [use mijit-beetle JIT compiler])], 39 | [case $withval in 40 | yes|no) ;; 41 | *) AC_MSG_ERROR([bad value $withval for mijit option]) ;; 42 | esac 43 | with_mijit=$withval], 44 | [with_mijit=no] 45 | ) 46 | if test "$with_mijit" = yes; then 47 | AC_PATH_PROG(CARGO, cargo) 48 | if test -z "$ac_cv_path_CARGO"; then 49 | AC_MSG_ERROR(cargo not found) 50 | fi 51 | AC_DEFINE(HAVE_MIJIT, 1, [Whether we are using Mijit.]) 52 | fi 53 | AM_CONDITIONAL([HAVE_MIJIT], [test "$with_mijit" = yes]) 54 | 55 | # Extra warnings with GCC 56 | AC_ARG_ENABLE([gcc-warnings], 57 | [AS_HELP_STRING([--disable-gcc-warnings], 58 | [turn off lots of GCC warnings])], 59 | [case $enableval in 60 | yes|no) ;; 61 | *) AC_MSG_ERROR([bad value $enableval for gcc-warnings option]) ;; 62 | esac 63 | gl_gcc_warnings=$enableval], 64 | [gl_gcc_warnings=yes] 65 | ) 66 | if test "$gl_gcc_warnings" = yes; then 67 | # Set up the list of undesired warnings. 68 | nw= 69 | nw="$nw -Wsystem-headers" # Don’t let system headers trigger warnings 70 | 71 | gl_MANYWARN_ALL_GCC([warnings]) 72 | 73 | # Enable all GCC warnings not in this list. 74 | gl_MANYWARN_COMPLEMENT([warnings], [$warnings], [$nw]) 75 | for w in $warnings; do 76 | gl_WARN_ADD([$w]) 77 | done 78 | 79 | # When compiling with GCC, prefer -isystem to -I when including system 80 | # include files, to avoid generating useless diagnostics for the files. 81 | ISYSTEM='-isystem ' 82 | else 83 | ISYSTEM='-I' 84 | fi 85 | AC_SUBST([ISYSTEM]) 86 | gl_INIT 87 | 88 | # Check features 89 | AC_C_BIGENDIAN 90 | 91 | # Generate output files 92 | AC_CONFIG_HEADERS([config.h]) 93 | AC_CONFIG_FILES([ 94 | Makefile 95 | lib/Makefile 96 | src/Makefile 97 | tests/Makefile 98 | doc/Makefile 99 | doc/cbeetle.tex 100 | doc/shell.tex 101 | ]) 102 | AC_OUTPUT 103 | -------------------------------------------------------------------------------- /doc/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((latex-mode . ((eval . (add-hook 'after-save-hook 2 | (lambda () (start-process "LaTeX make" nil "make")) 3 | nil t))))) 4 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.bbl 3 | *.blg 4 | *.fls 5 | *.fdb_latexmk 6 | *.log 7 | *.out 8 | *.synctex.gz 9 | /cbeetle.tex 10 | /shell.tex 11 | /primes 12 | -------------------------------------------------------------------------------- /doc/Makefile.am: -------------------------------------------------------------------------------- 1 | # Docs Makefile.am 2 | # 3 | # (c) Reuben Thomas 2011-2018 4 | # 5 | # The package is distributed under the GNU General Public License version 3, 6 | # or, at your option, any later version. 7 | # 8 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | # RISK. 10 | 11 | LATEX_SRCS = \ 12 | $(srcdir)/papers/intro.tex \ 13 | $(srcdir)/papers/litencode.tex \ 14 | $(srcdir)/papers/tradeoffs.tex \ 15 | $(srcdir)/bibtex/bib/rrt.bib \ 16 | $(srcdir)/bibtex/bib/vm.bib 17 | 18 | LATEX_PDFS = \ 19 | beetle.pdf \ 20 | cbeetle.pdf \ 21 | shell.pdf \ 22 | papers/intro.pdf \ 23 | papers/litencode.pdf \ 24 | papers/tradeoffs.pdf 25 | 26 | if HAVE_LATEXMK 27 | dist_doc_DATA = $(LATEX_PDFS) 28 | endif 29 | 30 | 31 | all: pdf 32 | 33 | pdf-local: $(LATEX_PDFS) 34 | 35 | MAINTAINERCLEANFILES = $(LATEX_PDFS) 36 | 37 | clean-local: 38 | $(LATEXMK) -r $(srcdir)/latexmkrc -C -f $(LATEX_SRCS) \ 39 | $(builddir)/beetle.tex $(builddir)/cbeetle.tex $(builddir)/shell.tex 40 | 41 | EXTRA_DIST = $(LATEX_SRCS) $(LATEX_PDFS) cbeetle.tex.in shell.tex.in latexmkrc 42 | 43 | # TODO: mv works around a bug in latexmk < 4.54, which sets $out_dir when it 44 | # shouldn't. Remove when we can require latexmk >= 4.54 (Ubuntu 20.04). 45 | .tex.pdf: 46 | env BIBINPUTS=$(abs_srcdir)/bibtex/bib $(LATEXMK) -r $(abs_srcdir)/latexmkrc -cd -f $< && \ 47 | if test -e `basename $@` -a ! `basename $@` -ef $@; then \ 48 | mv `basename $@` $@; \ 49 | fi 50 | -------------------------------------------------------------------------------- /doc/beetle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/doc/beetle.pdf -------------------------------------------------------------------------------- /doc/cbeetle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/doc/cbeetle.pdf -------------------------------------------------------------------------------- /doc/cbeetle.tex.in: -------------------------------------------------------------------------------- 1 | % 2 | % Documentation for C Beetle 3 | % 4 | % Reuben Thomas 5 | % 6 | % Started 1/12/94-8/5/95 7 | % 8 | 9 | \documentclass{article} 10 | \usepackage[british]{babel} 11 | \usepackage[utf8x]{inputenc} 12 | \usepackage{a4wide,newpxtext,booktabs,hyperref,siunitx} 13 | 14 | % Alter some default parameters for general typesetting 15 | 16 | \frenchspacing 17 | 18 | 19 | % Macros 20 | 21 | % Lay out an external interface call 22 | 23 | \newlength{\ifacewidth}\ifacewidth=\textwidth \advance\ifacewidth by -0.1in 24 | \newlength{\innerwidth}\innerwidth=\ifacewidth \advance\innerwidth by -0.5in 25 | \newcommand{\ifacec}[2]{\item[]\parbox{\ifacewidth}{\hspace*{2.5mm}{\tt #1}\\[0.5ex]\hspace*{0.4in}\parbox{\innerwidth}{#2}}} 26 | 27 | % Font for stack pictures; macro \spic includes italic correction 28 | 29 | \newfont{\spfont}{cmsltt10 scaled 1000} 30 | \newcommand{\spic}[1]{{\spfont\setlength{\baselineskip}{\normalbaselineskip}#1\/}} 31 | 32 | 33 | \title{An implementation of the Beetle virtual machine\\for POSIX\\version @VERSION@} 34 | \author{Reuben Thomas} 35 | \date{23rd September 2021} 36 | 37 | \begin{document} 38 | \maketitle 39 | 40 | 41 | \section{Introduction} 42 | 43 | The Beetle virtual machine~\cite{beetle} provides a portable environment 44 | for the \href{https://github.com/rrthomas/pforth}{pForth} Forth compiler, a compiler for ANSI Standard 45 | Forth~\cite{ANSIforth}. To port pForth to a new CPU architecture or operating system, only Beetle need be rewritten. However, even this can be 46 | avoided if Beetle is itself written in ISO C, since almost all systems have 47 | an ISO C compiler available for them. 48 | 49 | Writing Beetle in C necessarily leads to a loss of performance for a system 50 | which is already relatively slow by virtue of using a virtual machine 51 | rather than compiling native code. However, pForth is intended mainly as a 52 | didactic tool, offering a concrete Forth environment which may be used to 53 | explore the language, and particularly the implementation of the compiler, on 54 | a simple architecture designed to support Forth. Thus speed is not crucial, 55 | and on modern systems even a C implementation of Beetle can be expected to 56 | run at an acceptable speed. 57 | 58 | As well as the virtual machine, C Beetle provides a debugger, which is described in~\cite{beetleuiface}. 59 | 60 | The Beetle virtual machine is described in~\cite{beetle}. This paper only 61 | describes the features specific to this implementation. 62 | 63 | 64 | \section{Using C Beetle} 65 | 66 | This section describes how to compile C Beetle, and the exact manner in which 67 | the interface calls and Beetle's memory and registers should be accessed. 68 | 69 | 70 | \subsection{Configuration} 71 | \label{configuration} 72 | 73 | Beetle is written in ISO C99 using POSIX-1.2001 APIs. 74 | 75 | The Beetle virtual machine is inherently 32-bit, but will run happily on systems with larger (or smaller) addresses. 76 | 77 | 78 | \subsection{Compilation} 79 | 80 | Beetle's build system is written with GNU autotools, and the user 81 | needs only standard POSIX utilities to run it. Installation 82 | instructions are provided in the top-level file {\tt README.md}. 83 | 84 | 85 | \subsection{Registers and memory} 86 | 87 | Beetle's registers are declared in {\tt beetle.h}. Their names correspond to 88 | those given in~\cite[section~2.1]{beetle}, although some have been changed 89 | to meet the rules for C identifiers. C Beetle does not allocate any 90 | memory for Beetle, nor does it initialise any of the registers. C Beetle 91 | provides the interface call {\bf beetle\_init()} to do this (see 92 | section~\ref{usingcalls}). 93 | 94 | The variables {\tt EP}, {\tt I}, {\tt A}, {\tt M0}, {\tt MEMORY}, {\tt SP}, {\tt RP}, {\tt THROW}, {\tt BAD} and {\tt ADDRESS} 95 | correspond exactly with the Beetle registers they represent, and may be read 96 | and assigned to accordingly, bearing in mind the restrictions on their use 97 | given in~\cite{beetle}. {\tt THROW}, {\tt BAD} and {\tt ADDRESS} are mapped into Beetle's memory, so they are automatically updated when the corresponding memory locations are written to, and vice versa. {\tt CHECKED} is the constant $1$; it may be read but not assigned to. 98 | 99 | 100 | \subsection{Extra library calls} 101 | 102 | C Beetle provides the following {\tt LIB} calls. 103 | 104 | \subsubsection{Command-line arguments} 105 | 106 | Two calls are provided to access command-line arguments passed to C Beetle (excluding any that it interprets itself). They are copied from Gforth. 107 | 108 | \begin{center} 109 | \begin{tabular}{S[table-format=2.0]lll} \toprule 110 | {\bf Number} & {\bf Forth word} & {\bf Stack effect} & {\bf Description} \\ \midrule 111 | 0 & {\tt ARGC} & \spic{-- u} & the number of arguments \\ 112 | 1 & {\tt ARG} & \spic{u1 -- c-addr u2} & the \spic{u1}th argument \\ \bottomrule 113 | \end{tabular} 114 | \end{center} 115 | 116 | \subsubsection{Standard I/O streams} 117 | 118 | These {\tt LIB} calls provide access to POSIX standard input, output and error. Each call returns a corresponding file identifier. 119 | 120 | \begin{center} 121 | \begin{tabular}{S[table-format=2.0]l} \toprule 122 | {\bf Number} & {\bf POSIX file descriptor} \\ \midrule 123 | 2 & {\tt STDIN\_FILENO} \\ 124 | 3 & {\tt STDOUT\_FILENO} \\ 125 | 4 & {\tt STDERR\_FILENO} \\ \bottomrule 126 | \end{tabular} 127 | \end{center} 128 | 129 | \subsubsection{File system calls} 130 | 131 | The file system calls correspond directly to ANS Forth words, as defined in~\cite{ANSIforth}. 132 | 133 | \begin{center} 134 | \begin{tabular}{S[table-format=2.0]l} \toprule 135 | {\bf Number} & {\bf Forth word} \\ \midrule 136 | 5 & {\tt OPEN-FILE} \\ 137 | 6 & {\tt CLOSE-FILE} \\ 138 | 7 & {\tt READ-FILE} \\ 139 | 8 & {\tt WRITE-FILE} \\ 140 | 9 & {\tt FILE-POSITION} \\ 141 | 10 & {\tt REPOSITION-FILE} \\ 142 | 11 & {\tt FLUSH-FILE} \\ 143 | 12 & {\tt RENAME-FILE} \\ 144 | 13 & {\tt DELETE-FILE} \\ 145 | 14 & {\tt FILE-SIZE} \\ 146 | 15 & {\tt RESIZE-FILE} \\ 147 | 16 & {\tt FILE-STATUS} \\ 148 | \bottomrule 149 | \end{tabular} 150 | \end{center} 151 | 152 | The implementation-dependent cell returned by {\tt FILE-STATUS} contains the POSIX protection bits, given by the {\tt st\_mode} member of the {\tt struct stat} returned for the given file descriptor. 153 | 154 | File access methods are bit-masks, composed as follows: 155 | 156 | \begin{center} 157 | \begin{tabular}{cc} \toprule 158 | \rule[-2mm]{0mm}{6mm}\bf Bit value & \bf Meaning \\ \midrule 159 | 1 & read \\ 160 | 2 & write \\ 161 | 4 & binary mode \\ \bottomrule 162 | \end{tabular} 163 | \end{center} 164 | 165 | To create a file, set both read and write bits to zero when calling {\tt OPEN-FILE}. 166 | 167 | 168 | \subsection{Using the interface calls} 169 | \label{usingcalls} 170 | 171 | The operation of the specified interface calls is given in~\cite{beetle}. Here, the C prototypes corresponding to the idealised prototypes used in~\cite{beetle} are given. The names are prefixed with {\bf beetle\_}. 172 | 173 | Files to be loaded and saved are passed as C file descriptors. Thus, the 174 | calling program must itself open and close the files. 175 | 176 | \begin{description} 177 | \ifacec{uint8\_t *native\_address(beetle\_UCELL address, bool writable)}{Returns {\tt NULL} when the address is invalid, or the writable flag is true and the address is read-only.} 178 | \ifacec{beetle\_CELL run(void)}{The reason code returned by {\bf beetle\_run()} is a Beetle 179 | cell.} 180 | \ifacec{beetle\_CELL beetle\_single\_step(void)}{The reason code returned by {\bf beetle\_single\_step()} 181 | is a Beetle cell.} 182 | \ifacec{int beetle\_load\_object(FILE *file, beetle\_UCELL address)}{If a file system error 183 | occurs, the return code is -3. As an extension to the specification, if an object file starts with the bytes $35$, $33$ (\texttt{\#!}), then it is assumed to be the start of a UNIX-style “hash bang” line, and the file contents up to and including the first newline character ($10$) is ignored.} 184 | \end{description} 185 | 186 | In addition to the required interface calls C Beetle provides an initialisation routine {\bf beetle\_init()} which, given a cell array and its size, initialises Beetle: 187 | 188 | \begin{description} 189 | \ifacec{int beetle\_init(beetle\_CELL *b\_array, size\_t size)}{{\tt size} is 190 | the size of {\tt b\_array} in {\em cells} (not bytes). The return value is $-1$ if {\tt b\_array} is {\tt NULL}, and $0$ otherwise. 191 | All the registers are initialised as per~\cite{beetle}.} 192 | \end{description} 193 | 194 | The following routines give easier access to Beetle’s address space at the byte and cell level. On success, they return $0$, and on failure, the relevant exception code. 195 | 196 | \begin{description} 197 | \ifacec{int beetle\_load\_cell(beetle\_UCELL address, beetle\_CELL *value)}{Load the cell at the given address into the given {\tt beetle\_CELL *}.} 198 | \ifacec{int beetle\_store\_cell(beetle\_UCELL address, beetle\_CELL value)}{Store the given {\tt CELL} value at the given address.} 199 | \ifacec{int beetle\_load\_byte(beetle\_UCELL address, beetle\_BYTE *value)}{Load the byte at the given address into the given {\tt beetle\_BYTE *}.} 200 | \ifacec{int beetle\_store\_byte(beetle\_UCELL address, beetle\_BYTE value)}{Store the given {\tt beetle\_BYTE} value at the given address.} 201 | \end{description} 202 | 203 | The following routines allow direct native access to Beetle’s memory. On success, $0$ is returned; if {\tt to} if not less than {\tt from}, or the addresses are not valid, $-1$ is returned; if either address is unaligned, or some other error occurs, a memory exception code is returned. 204 | 205 | \begin{description} 206 | \ifacec{int beetle\_pre\_dma(beetle\_UCELL from, beetle\_UCELL to)}{Convert the given range to native byte order, so that it can be read and written directly.} 207 | \ifacec{int beetle\_post\_dma(beetle\_UCELL from, beetle\_UCELL to)}{Convert the given range to Beetle byte order, so that it can be used by Beetle after a direct access.} 208 | \end{description} 209 | 210 | The following routine allows the calling program to register command-line arguments that can be retrieved by the {\tt ARG} and {\tt ARGC} {\tt LIB} calls. 211 | 212 | \begin{description} 213 | \ifacec{int beetle\_register\_args(int argc, const char *argv[])}{Registers the given argument list, which has the same format as that supplied to {\bf main()}. Returns $0$ on success and $-1$ if memory could not be allocated.} 214 | \end{description} 215 | 216 | Programs which use C Beetle's interface must {\tt \#include} the header file 217 | {\tt beetle.h} and be linked with the Beetle library. {\tt 218 | beetle\_opcodes.h}, which contains an enumeration type of Beetle's instruction set, 219 | and {\tt beetle\_debug.h}, which contains useful debugging functions such as 220 | disassembly, may also be useful; they are not documented here. 221 | 222 | 223 | \subsection{Other extras provided by C Beetle} 224 | 225 | C Beetle provides the following extra quantities and macro in {\tt beetle.h} 226 | which are useful for programming with Beetle: 227 | 228 | \begin{description} 229 | \item[{\tt BEETLE\_TRUE}:] a cell with all bits set, which Beetle uses as a true 230 | flag. 231 | \item[{\tt BEETLE\_FALSE}:] a cell with all bits clear, which Beetle uses as a 232 | false flag. 233 | \item[{\tt beetle\_CELL\_W}:] the width of a cell in bytes (4). 234 | \item[{\tt beetle\_POINTER\_W}:] the width of a machine pointer in cells. 235 | \item[{\tt beetle\_NEXT}:] a macro which performs the action of the {\tt NEXT} 236 | instruction. 237 | \item[{\tt beetle\_CELL\_pointer}:] a union with members {\tt beetle\_CELL cells[beetle\_POINTER\_W]} and {\tt void (*pointer)(void)}, which allow a function pointer suitable for the {\tt LINK} instruction to be easily stored and retrieved. It is assumed that the pointer is pushed on to the stack starting with {\tt cells[0]} and ending with {\tt cells[beetle\_POINTER\_W~$-$~1]}. 238 | \end{description} 239 | 240 | 241 | \bibliographystyle{plain} 242 | \bibliography{vm,rrt} 243 | 244 | 245 | \end{document} 246 | -------------------------------------------------------------------------------- /doc/latexmkrc: -------------------------------------------------------------------------------- 1 | # latexmk -*- mode: perl -*- 2 | 3 | $pdf_mode = 1; 4 | $postscript_mode = $dvi_mode = 0; 5 | $bibtex_use = 2; 6 | $max_repeat = 9; -------------------------------------------------------------------------------- /doc/papers/intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/doc/papers/intro.pdf -------------------------------------------------------------------------------- /doc/papers/intro.tex: -------------------------------------------------------------------------------- 1 | % 2 | % An Introduction to the Beetle Forth Virtual Processor 3 | % 4 | % Short introductory paper 5 | % 6 | % Reuben Thomas 13-24/11/96 7 | % 8 | 9 | 10 | \documentclass{article} 11 | \usepackage[british]{babel} 12 | \usepackage{a4,newlfont} 13 | 14 | 15 | % Alter some default parameters for general typesetting 16 | 17 | \frenchspacing 18 | 19 | 20 | % New commands 21 | 22 | \newcommand{\conc}[1]{\texttt{\textup{#1}}} 23 | 24 | 25 | 26 | \title{An Introduction to the Beetle Forth Virtual Processor} 27 | \author{Reuben Thomas} 28 | \date{24th November 1996} 29 | \begin{document} 30 | \maketitle 31 | 32 | \begin{abstract} 33 | Beetle is a virtual processor designed for the Forth language. It uses a 34 | modified byte-stream code designed for efficient execution which is binary 35 | portable between implementations. It has been implemented in C and assembler. 36 | The C implementation is completely machine-independent with the exception of 37 | interactive input and output; the assembler version runs the supplied Forth 38 | compiler at up to half the speed of the corresponding native code compiler 39 | and generates more compact code. Beetle is designed to be embedded in other 40 | programs; a simple debugger has been written to demonstrate this ability. 41 | Beetle can be configured to perform bounds checking on all memory references. 42 | A standard I/O library is implemented; access to native code routines is also 43 | possible, allowing Forth and C programs to call each other. 44 | \end{abstract} 45 | 46 | 47 | 48 | \section{Introduction} 49 | 50 | Ever since the invention of high-level languages, one of the most popular 51 | implementation methods has been to use a virtual processor, often called an 52 | interpreter~\cite{intcoproc}. A virtual processor, or ``VP'', usually 53 | resembles a real processor, reading a virtual instruction stream and calling 54 | appropriate routines to carry out the actions of the virtual instructions. 55 | VPs are often designed to support a particular language, so that once a 56 | VP has been produced writing a compiler is straightforward, and porting the 57 | compiler requires little more effort than porting the VP. This method is 58 | commonly used for functional languages such as LISP and 59 | Miranda~\cite{lispemul,combreduc}, and especially for prototyping new 60 | compilers~\cite{parscheme,aplgen}. 61 | 62 | Beetle is just such a VP, and the language it supports is 63 | Forth~\cite{starting4th}. Forth is a simple stack-based language which is 64 | most commonly used in embedded systems; however, Beetle is more suited to 65 | general applications, Forth compiler development and teaching, as it lacks 66 | the speed and low-level hardware access required for embedded systems. 67 | 68 | Add to the discussion above the fact that Forth is often implemented on top 69 | of a virtual machine~\cite{threaded}, and it is hard to see what could be 70 | interesting or novel about Beetle. The rest of this paper is an attempt to 71 | show that Beetle is worthy of interest, but before proceeding to describe it, 72 | three points should be made. First, while none of Beetle's features is 73 | revolutionary, they have not to the author's knowledge been combined 74 | before.\footnote{The same is true of the much more illustrious Java virtual 75 | processor; indeed, the fact that the techniques involved in its construction 76 | are all tried and tested is its greatest strength.} Secondly, virtual 77 | processors are often forgotten as they lie buried under the compilers they 78 | were designed to support, and there are few detailed reports available on the 79 | design and performance of specific VPs (\cite{intcoproc} contains a good 80 | bibliography of those which do exist).\footnote{Recent literature includes 81 | interesting reports on new types of virtual processor~\cite{virttime}, but 82 | while these are innovative and worthy of note, they are mainly relevant to 83 | researchers in their application areas, and not to VP designers in general.} 84 | Thirdly, after a period of quiescence, VPs are undergoing something of a 85 | renaissance, and must be reassessed in the context of the new uses to which 86 | they are put and machines on which they are run. 87 | 88 | 89 | 90 | \section{The virtual processor design} 91 | 92 | Beetle's architecture is similar to that of a real processor. It is 93 | stack-based, and all computation takes place on the data stack, so it has no 94 | general-purpose registers. There is also a return stack. All instructions are 95 | represented by one-byte opcodes. Instructions take either zero or one 96 | operand. 97 | 98 | 99 | \subsection{Memory} 100 | 101 | The memory is an array of four-byte words. The bytes in a word may be stored 102 | in either little-endian or big-endian order, so word addressing is 103 | always efficient, but byte addressing is little-endian, so that it is 104 | identical in all implementations. The penalty is a single machine instruction 105 | on big-endian machines to invert the bottom two bits of the address of a byte 106 | reference. 107 | 108 | 109 | \subsection{Registers} 110 | 111 | Beetle has a program counter, \conc{EP} (``\conc{E}xecution \conc{P}ointer'') 112 | and two stack pointers: \conc{SP}, the data \conc{S}tack \conc{P}ointer, and 113 | \conc{RP}, the \conc{R}eturn stack \conc{P}ointer. \conc{I} holds the current 114 | \conc{I}nstruction, and \conc{A}, the instruction \conc{A}ccumulator, the 115 | next few instructions to execute. There are several other more specialised 116 | registers. 117 | 118 | 119 | \subsection{Execution} 120 | 121 | When Beetle is started, it performs the following execution cycle: 122 | 123 | \begin{it} 124 | \begin{tabbing} 125 | \hspace{0.5in}begin\\ 126 | \hspace{0.75in}copy the least-significant byte of \conc{A} to \conc{I}\\ 127 | \hspace{0.75in}shift \conc{A} arithmetically 8 bits to the right\\ 128 | \hspace{0.75in}execute the instruction in \conc{I}\\ 129 | \hspace{0.5in}repeat 130 | \end{tabbing} 131 | \end{it} 132 | 133 | \noindent This demonstrates Beetle's main adaptation for efficient execution 134 | on modern pro\-cessors, which is to load instructions severally rather than 135 | singly. \conc{A} is four bytes wide; on most modern processors it is at least 136 | as fast to load a four-byte word from memory as to load four single bytes. 137 | When the accumulator becomes empty the value zero or 255 is copied to 138 | \conc{I}; these are the opcodes of the instruction \conc{NEXT}, which causes 139 | the word pointed to by \conc{EP} to be loaded into \conc{A}. 140 | 141 | It might seem that \conc{NEXT} would therefore be executed at least every 142 | fifth cycle, but since instructions such as branches perform an implicit 143 | instruction fetch it is in fact only executed as about 10\% of instructions 144 | obeyed. 145 | 146 | 147 | \subsection{Operands} 148 | 149 | The only operands are numeric literals and branch addresses. Where possible 150 | these are packed into the instruction word directly after the instruction 151 | opcode; addresses are turned into offsets for compactness. Such immediate 152 | operands always occupy the rest of the instruction word. If the operand is 153 | too big, then it is placed in the next available word; further instruction 154 | opcodes may still be placed in the current word. 155 | 156 | In the execution cycle \conc{A} is shifted arithmetically rather than 157 | logically. This allows negative literals and branch offsets to be used 158 | without needing extra opcodes. By shifting \conc{A} before the instruction is 159 | executed immediate operands are accessible to their instructions with no 160 | further decoding required. 161 | 162 | 163 | \subsection{Implementability} 164 | 165 | Because of its simple design which uses quantities no smaller than a byte 166 | and no bigger than a four-byte word, Beetle is easy to implement, whether in 167 | a high-level language or assembler. In a high-level implementation, Beetle's 168 | registers map obviously on to variables and the memory can be represented as 169 | a byte array, manipulated with array operations. All these operations are 170 | optimised well by optimising compilers. In assembler Beetle's registers map 171 | naturally on to machine registers, and ordinary memory addressing 172 | instructions can be used to manipulate its memory. 173 | 174 | The non-recursive design means that static allocation techniques can be used, 175 | which keeps assembler implementations simple and makes them more likely to be 176 | correct. The use of twos-complement arithmetic again leads to a natural and 177 | efficient implementation; few computers still use other forms of arithmetic. 178 | 179 | 180 | 181 | \section{Portability} 182 | 183 | The greatest benefit of Beetle's implementability in high-level languages is 184 | that it can easily be made portable. This was the main goal of the C 185 | implementation, which is written entirely in ANSI C, and uses the standard 186 | libraries almost exclusively. The only exception was forced by the nature of 187 | Forth: since it is interactive, it requires unbuffered character input and 188 | output. These can sometimes be achieved using the ANSI libraries, but it is 189 | not guaranteed; on most operating systems (apart from UNIX) simple routines 190 | are available to input and output single characters. 191 | 192 | Thus character input and output macros must be supplied along with the 193 | endianness and a few other machine characteristics for each machine on which 194 | Beetle is to be compiled. The configuration is isolated in a special header 195 | file, several of which have been prepared for various operating systems. 196 | Special arrangements are made for UNIX, and extra code is supplied to 197 | implement unbuffered input and output. 198 | 199 | C Beetle has to date been compiled and tested successfully on five different 200 | operating systems, including three versions of UNIX. 201 | 202 | Beetle's simple design also makes it easy to write hand-coded implementations 203 | quickly: the ARM version was completed in a week. This is still a short time 204 | to transport an entire Forth system to a new environment, and as will be seen 205 | in section~\ref{performance}, it gives markedly better performance. 206 | 207 | 208 | \section{Compiler support} 209 | \label{support} 210 | 211 | Conventional Forth compilers use indirect threaded code, in which Forth words 212 | (the equivalent of functions or procedures in other languages) are compiled 213 | as lists of addresses pointing to other words. Each word has a code field, 214 | which contains a pointer to code to execute the word; in most words, this is 215 | the address interpreter, a minimal VP which interprets the list of addresses, 216 | finding their code fields in turn and branching to them. In primitive words 217 | written in assembler the code field will point to the corresponding machine 218 | code. 219 | 220 | This VP is so simple that it consists of just a few instructions. It is 221 | flexible in that it can be adapted to many designs of compiler (and indeed, 222 | many languages). On the other hand its code is not portable, nor particularly 223 | dense, as even primitive instructions such as those provided in Beetle's 224 | instruction set occupy a machine word, generally four bytes on modern 225 | processors.\footnote{It is often claimed that Forth translates into 226 | particularly dense object code, but this is doubtful on modern machines. One 227 | explanation given is that Forth programmers simply write smaller programs 228 | than programmers using other languages; it should also be borne in mind that 229 | Forth has a much bigger advantage over other languages on 8-bit processors, 230 | on which addresses are only sixteen bits long, and relatively much denser 231 | compared with machine code than on 32-bit or 64-bit processors.} 232 | 233 | By providing a range of specialised instructions (most of them corresponding 234 | directly to ANSI Standard words), Beetle allows compact and portable code to 235 | be generated. It also simplifies the implementation of compilers, at the 236 | expense of fixing some design choices such as the number of stacks, and 237 | forcing the use of the return stack for loop indices and counts. 238 | 239 | Beetle provides most of the ANSI Core Word Set arithmetic, logical and memory 240 | access words as native instructions, and directly supports all the usual 241 | Forth looping constructs, as well as the \conc{CREATE\dots DOES>} datatype 242 | declaration mechanism. Support is also provided for exceptions, though not 243 | for local variables. This practice of providing instructions with relatively 244 | high semantic content lessens the overheads of interpretation, as a lower 245 | proportion of time is spent fetching and decoding instructions. 246 | 247 | 248 | 249 | \section{Embedding and safety} 250 | 251 | Beetle is designed to be used as an interpreter embedded in other programs. 252 | The C implementation provides a header that programs may include to use 253 | Beetle; at the moment only one instantiation of the interpreter is allowed, 254 | but this restriction could easily be lifted. The memory and all the registers 255 | are available to the program to be inspected and manipulated, so the C 256 | program can both control Beetle, and, by means of Beetle's \conc{LINK} 257 | instruction, be called by it, arguments and return values being passed on 258 | Beetle's data stack. The facilities provided are enough to write a debugger, 259 | and a simple debugger was written to aid the development of the C 260 | implementation of Beetle and the porting of the Forth compiler to Beetle. 261 | 262 | One of Beetle's registers, \conc{CHECKED}, controls whether address checking 263 | is performed; in the C and assembler implementations its value is fixed at 264 | compile time. When enabled, address checking is performed on all memory 265 | references, and out-of-bounds and unaligned memory references are trapped and 266 | reported to the calling program. In this situation, Beetle cannot corrupt the 267 | program that controls it directly, though it can cause corruption or a crash 268 | by indiscriminate use of \conc{LINK}. 269 | 270 | These features make Beetle a good candidate for an embedded interpreter, 271 | whether to implement an application-specific scripting language, or to 272 | provide a safe way for an application to generate and run code on the fly. 273 | 274 | 275 | 276 | \section{Performance} 277 | \label{performance} 278 | 279 | Beetle cannot hope to outperform native machine code because of the 280 | interpretive overhead. The most important question to address is whether it 281 | runs fast enough; it is also interesting to see what the interpretive 282 | slow-down is. 283 | 284 | Three main benchmarks were run: two computation-intensive prime-finding 285 | programs, and a compiler testing program, which was read from disk as it 286 | progressed, and also exercised the input-output functions quite heavily. The 287 | timings discussed below are those taken when Beetle was peforming address 288 | checks. 289 | 290 | The timings were taken on a machine with an ARM610 processor rated at about 291 | 20mips. The C implementation of Beetle ranged between about 0.4mips and 292 | 0.5mips, and the native implementation averaged around 1mips; the raw 293 | interpretive slow-down is between 20 and 40 times. The actual slow-down was 294 | much less: when the benchmarks were run on the native ARM version of the 295 | Forth compiler, they ran only 2.3 times faster than native Beetle, and 7.9 296 | times faster than C Beetle. It should also be remembered that Beetle 297 | input-output operations, such as reading bytes from a file, count as a single 298 | interpretive instruction. 299 | 300 | The tests also completed in a reasonable time on the native Beetle, though on 301 | C Beetle they were rather slow, taking 200s to find all the primes up to 302 | 800,000, compared with 54s for native Beetle, 20s for a native Forth compiler 303 | and 2.0s for a C translation, compiled with optimisations. Only Beetle 304 | performed address checks, and the Forth compilers did not optimise; indeed, 305 | Beetle's virtual code cannot be optimised much. This is an advantage inasmuch 306 | as a na\"{\i}ve compiler will produce near-optimal code; however, since the 307 | instructions are low-level, many are executed (67.8 million in the benchmark 308 | under discussion) and the interpretive overhead remains high. This contrasts 309 | with languages such as APL, in which VPs compete with native compilers for 310 | speed since they spend far more time performing instructions than decoding 311 | them~\cite{intcoproc}. 312 | 313 | Nevertheless, Beetle is more than adequately fast for interactive program 314 | development in the usual Forth style; since its compiler is ANSI-compliant, 315 | demanding programs developed on it could be recompiled with an optimising 316 | compiler to obtain better performance. 317 | 318 | 319 | 320 | \section{Conclusion} 321 | 322 | The Beetle system allows Forth programs to be developed in a safe 323 | environment. With the addition of some more input-output primitives, and 324 | extra instructions to support floating point arithmetic and the few other 325 | unsupported parts of the ANSI Forth Standard, the compiler could easily be 326 | extended to provide a full Forth development environment. Programs are 327 | instantly portable to a wide range of machines, and ports to new machines are 328 | simple. With a little more work, execution can be dramatically improved by 329 | hand-coding the VP. Interworking with C and other high-level languages is 330 | also straightforward, and Beetle can be embedded in application programs to 331 | drive a command language or run code generated on the fly. 332 | 333 | Beetle's design is simple, but closely adapted to modern processors. It is 334 | easy to implement in high-level languages or assembler, and performs well in 335 | both cases. At the beginning of a new era of VPs, Beetle is a useful starting 336 | point. 337 | 338 | 339 | 340 | \bibliographystyle{plain} 341 | \bibliography{vm} 342 | 343 | 344 | \end{document} 345 | -------------------------------------------------------------------------------- /doc/papers/litencode.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/doc/papers/litencode.pdf -------------------------------------------------------------------------------- /doc/papers/litencode.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[british]{babel} 3 | 4 | \makeatletter 5 | \usepackage{a4wide,babel,array,booktabs} 6 | \newenvironment{code}% 7 | {\begin{small}\begin{tabular}{>{\tt}ll>{\tt}ll}\toprule% 8 | \multicolumn{2}{c}{\bf 68000} & \multicolumn{2}{c}{\bf ARM} \\% 9 | \midrule}% 10 | {\bottomrule\end{tabular}\end{small}} 11 | \makeatother 12 | 13 | \title{Encoding literals in a portable byte-stream interpreter} 14 | \author{Reuben Thomas} 15 | \date{18th October 1994} 16 | 17 | \begin{document} 18 | \maketitle 19 | 20 | \begin{abstract} 21 | Three approaches to the problem of encoding literal numbers and addresses in a 22 | portable byte-stream virtual machine interpreter are discussed, and one recommended 23 | for the case where the interpreter is written in machine-code (or optimally compiled). 24 | It is assumed that the instruction fetch loads a word of instructions from memory, 25 | a useful optimisation on modern processors. Analysis of the time and space costs 26 | for the interpreter program are made with reference to the ARM and 68000 processor 27 | series. 28 | \end{abstract} 29 | 30 | \section{The problem} 31 | 32 | In the design of a portable byte-stream virtual processor the method of encoding 33 | programs is fixed, so no optimisation can be performed for specific machines. 34 | However, it is still necessary to design for optimum execution time and space 35 | requirement. Although efficient space usage is less important in modern hardware 36 | processors than in previous generations, it is still vitally important for interpreters, 37 | as one of their largest overheads is the time taken to perform instruction fetches, 38 | because memory references are typically among the most expensive instructions 39 | in modern systems. 40 | 41 | In a byte-stream interpreter, the instruction codes are usually all one byte long, 42 | as this allows a maximum of 256 different instructions, which can be coded in 43 | an interpreter small enough to fit in a hardware cache, and hence greatly improve 44 | performance. (Smaller than one-byte codes are not byte-stream codes!) Optimisations 45 | here are bound up with the design of the processor, and are difficult to treat 46 | generally. So we are left with operands. Again, some of these are of fixed length, 47 | such as register numbers, and their optimisation interacts with the processor's 48 | design. Two, however, may not be of fixed length: relative addresses, and numbers. 49 | I shall refer to these collectively as literals. 50 | 51 | This paper deals with the optimisation of the encoding of literals in order to 52 | minimise the space spent on their storage, and the time spent on instruction and 53 | operand fetch. We assume that instructions and immediate operands are mixed, which 54 | seems to be the dominant method, and use a simple table-lookup dispatch method 55 | which is short enough to be appended to the end of every instruction action routine. 56 | This is a common method, which increases the speed of the interpreter at the expense 57 | of extra code. However, this usage is not central to the optimisation methods 58 | discussed. We further assume that a word is four bytes. The methods discussed 59 | would need modification for larger word sizes; the first two may be used for two-byte 60 | word machines. On an eight-bit processor, space rather than time optimisation 61 | is usually the prime concern, and a simple system with a different opcode for 62 | each literal size would be used, with literals being stored directly after their 63 | instructions. Of course, extremely common literals such as 0 and \( -1 \) may 64 | usefully be encoded in the instruction itself. 65 | 66 | The discussion is based on optimal machine code, and the idiosyncrasies of particular 67 | compilers are not taken into account. It is the author's opinion that interpreters 68 | should only be written in a high-level language to facilitate porting to machines 69 | on which they do not have a native implementation, and possibly as a starting 70 | point for such an implementation. Unless one is prepared to spend a great deal 71 | of time studying a particular compiler, or has a particularly good compiler, compiled 72 | interpreters will perform much worse than those which are hand-coded. 73 | 74 | 75 | \section{Possible solutions } 76 | 77 | The most obvious optimisation to make is to allow literals to be stored in different 78 | amounts of space. This is most effective with address literals if they are relative 79 | addresses, and this will be assumed in the discussion; the same optimisations 80 | can be applied to absolute addresses (without the need to handle the sign), but 81 | will probably be less useful. Signed numeric literals are probably necessary, 82 | although negative numbers are less common than negative relative addresses, and 83 | small numbers are more common than small absolute addresses in programs. 84 | 85 | If literals are to occupy variable amounts of space, it is most sensible to let 86 | them vary in multiples of one byte up to a machine word. As they fit on byte boundaries, 87 | they do not interfere with decoding (because instructions and operands do not 88 | cross word boundaries), or waste space by leaving bits unused. Longer literals 89 | may as well be multiples of one word long, as they are rare and so do not waste 90 | much space, neither do they then incur decoding penalties. They may then be treated 91 | for the purposes of optimisation as a series of one-word literals, and in any 92 | case there is little point optimising them because of their rarity. 93 | 94 | Similarly, any literals of one word or longer are best aligned on word boundaries, 95 | as on most processors this will result in faster loading, as literals spanning 96 | word boundaries have to be loaded as two words and `stapled' together. 97 | 98 | 99 | \subsection{The base solution\label{base}} 100 | 101 | The simplest time optimisation is to read instructions until a literal is needed, 102 | then to fetch it from the next word-aligned address, and to continue reading instructions 103 | from the word after that. This is obviously inefficient in space, and we will 104 | look at ways to plug the gaps without greatly slowing down the literal fetch. 105 | 106 | 107 | \subsection{Simple word-aligned literals\label{simple}} 108 | 109 | This method works like the na\"ive solution, except that instructions are always 110 | stored in the next free byte, so that no gaps are left in the code. All literals 111 | are a word long. 112 | 113 | There are two obvious implementations of this method: first, a pointer may be 114 | kept to the next word which could hold a literal; second, the interpreter fetches 115 | a word of instructions at a time.% 116 | \footnote{This may be a good optimisation, as many modern processors can only perform word-aligned 117 | fetches, and even on those which can fetch single bytes, it is as fast to fetch 118 | a whole word. 119 | } This leaves the instruction pointer free to point at the next potential literal. 120 | We use IP to denote the register holding the instruction pointer, I the register 121 | holding the instruction code, LP the literal pointer of the first scheme, and 122 | A the instruction accumulator of the second (where each word of instructions is 123 | loaded). T denotes the base address of a list of the addresses of the instruction 124 | routines; X denotes a temporary register. 125 | 126 | The second scheme presents a further choice: how to tell when A is empty. A loop 127 | might be used (even unrolled) to count the number of instructions left; however, 128 | such a dispatcher is not short enough to put after every instruction in the interpreter. 129 | Our solution is to use one or two extra opcodes which perform an instruction fetch. 130 | This results in compact code with little time penalty. 131 | 132 | The code for the first scheme looks like this: 133 | 134 | \begin{center} 135 | \begin{code} 136 | 137 | move.b (IP)+,I & get opcode & ldrb I,{[}IP{]},\#1 & get opcode\\ 138 | 139 | move.b IP,X & see if this is the last & tst IP,\#3 & if this is the last instruction\\ 140 | 141 | and.b \#3,X & instruction in the & & in the current word,\\ 142 | 143 | cmp.b \#0,X & current word & moveq IP,LP & copy LP to IP and\\ 144 | 145 | bne.s skip & if not, skip add else & addeq LP,LP,\#4 & make LP point to the next 146 | word\\ 147 | 148 | move.l LP,IP & copy LP to IP and & ldr pc,{[}T,I,asl\#2{]} & and dispatch instruction\\ 149 | 150 | adda.w \#4,LP & make LP point to the &&\\ 151 | 152 | skip: & next word &&\\ 153 | 154 | asl.w \#2,I & use opcode for table &&\\ 155 | 156 | jmp 0(T,I) & lookup, and dispatch &&\\ 157 | 158 | & instruction &&\\ 159 | 160 | \end{code} 161 | \end{center} 162 | \noindent On the 68000, the code takes 76 cycles if the branch is taken, and 62 163 | if not (65.5 on average); on the ARM, the code always takes 9 cycles.% 164 | \footnote{Some of the cycles will take different times depending on cache state; for simplicity 165 | we assume they all take the same time, i.e. that all memory references are found 166 | in the cache; this is not unreasonable, as we hope that the interpreter will be 167 | held mainly in the cache. References to the program being interpreted will be 168 | slower; this is taken into account in the discussion. 169 | } The code occupies 28~bytes on the 68000 and 24~bytes on the ARM. Adding this 170 | code to every instruction adds 7~Kb to the interpreter on the 68000, and 6~Kb 171 | on the ARM. This is not acceptable. 172 | 173 | The code for the second scheme is: 174 | 175 | \begin{center} 176 | \begin{code} 177 | 178 | lsr.l \#8,A & get next instruction & mov A,A,lsr \#8 & get next instruction\\ 179 | 180 | move.b A,I && and I,A,\#\&FF &\\ 181 | 182 | asl.w I, \#2 & make offset and & ldr pc,{[}T,I,asl\#2{]} & and dispatch it \\ 183 | 184 | jmp 0(T,I) & dispatch instruction &&\\ 185 | 186 | \end{code} 187 | \end{center} 188 | \noindent In addition, extra code is needed for opcode 0: 189 | 190 | \begin{center} 191 | \begin{code} 192 | 193 | move.l (IP)+,A & get next four opcodes & ldr A, {[}E{]}, \#4 & get next four opcodes\\ 194 | 195 | move.b A,I & get the first opcode & and I,A,\#\&FF & get the first opcode \\ 196 | 197 | asl.w I,\#2 & make offset and & ldr pc, {[}T,I,asl\#2{]} & and dispatch it \\ 198 | 199 | jmp 0(T,I) & dispatch instruction &&\\ 200 | 201 | \end{code} 202 | \end{center} 203 | \noindent The code for opcode 0 is executed every fourth instruction in addition 204 | to the normal dispatch code. On the 68000, the normal dispatch code takes 44 cycles, 205 | and the additional code 36, giving 53 cycles on average; the ARM code takes 5 206 | cycles per instruction with an additional 6 for opcode 0, giving an average of 207 | 6.5 cycles. The 0 opcode method also makes fewer memory references to the program 208 | being interpreted, giving an additional speed advantage. Also, most modern processors 209 | have a barrel shifter, unlike the 68000, giving even faster execution. 210 | 211 | The space requirements are much lower than for the other method, which also uses 212 | an extra permanent register, and an extra temporary register. For the 0 opcode 213 | method, only 3 Kb is added to each interpreter. Since the individual instructions 214 | will typically also be extremely short, this may be acceptable. 215 | 216 | 217 | \subsection{Word-aligned literals with end-of-word fillers\label{fillers}} 218 | 219 | By extending the instruction set of the interpreter and making a slight change 220 | to the dispatch code given above, we can significantly reduce the space occupied 221 | by interpreter code. 222 | 223 | The change is to allow literals to be stored directly after the instruction opcode 224 | whose parameter they are, taking up the remainder of the instruction word. The 225 | space available will range from one to three bytes. To do this, two opcodes are 226 | needed for every instruction which has a literal argument. If there are not enough 227 | spare opcodes, it may be worth only doubling those which are used most frequently. 228 | 229 | The dispatch code is exactly the same as before, except that if signed literals 230 | are required, arithmetic rather than logical shifts must be used. On the two processors 231 | used here, and on most other processors, this results in identical timings for 232 | the code given (which should have lsr changed to asr). The code for the version 233 | of the instruction which takes the literal from the instruction word must now 234 | include the opcode 0 case dispatch code. It can access the literal merely by shifting 235 | the I register eight bits right (arithmetically or logically as appropriate). 236 | 237 | Apart from its effect on the opcode allocation and a slight increase in the size 238 | of the interpreter, the only restriction of this method is that all literals must 239 | either be signed or unsigned. Thus it is much more useful with relative addressing. 240 | 241 | 242 | \subsection{Variable-size literals\label{variable}} 243 | 244 | The logical extension of the method in \ref{fillers} is to use separate opcodes 245 | for literal sizes from one to four bytes long. However, this is not sensible. 246 | Since decoding literals from the middle of an instruction word is slower than 247 | decoding those stored at the end, the opcodes introduced in \ref{fillers} should 248 | be used whenever this occurs, i.e. for all 3-byte offsets, half of the 2-byte 249 | offsets, and one third of the 1-byte offsets. Thus the opcode for 2-byte offsets 250 | is used in only a quarter of the cases where the offset could be fitted into two 251 | bytes. To decode a 1-byte literal the following code is used: 252 | 253 | \begin{center} 254 | \begin{code} 255 | 256 | move.b A,T & get literal & mov T,A,lsl\#24 & get literal \\ 257 | 258 | ext.w T & sign extend to two bytes & mov T,T,asr\#24 & sign extend it \\ 259 | 260 | ext.l T & sign extend to a word & mov A,A,asr\#8 & ensure literal is not executed \\ 261 | 262 | asr.l \#8,A & ensure literal is not executed &&\\ 263 | 264 | \end{code} 265 | \end{center} 266 | \noindent This code takes three cycles on the ARM, two more than the shift required 267 | to access a literal stored at the end of an instruction word, and 20 cycles on 268 | the 68000, 12 more than the end-of-word case. 269 | 270 | 271 | \section{Evaluation} 272 | 273 | All three methods give both time and speed benefits over the base method of \ref{base}. 274 | Each is suited to different conditions, depending on the type of addressing used. 275 | \ref{simple} is probably the most sensible to use for absolute addressing unless 276 | the address space is small, as it the extra optimisations of the other methods 277 | will be rarely used, waste valuable opcodes and increase the size of the interpreter 278 | unnecessarily. Method~\ref{fillers} is probably best if absolute addressing 279 | relative to a base register is used, as then most of the addresses will probably 280 | fit into three bytes. Method \ref{variable} may be best for relative addressing, 281 | but it is not clear whether the speed loss in processing 1-byte literals will 282 | be offset by the compactness of the code. Also, it makes the interpreter larger 283 | and uses more opcodes; depending on the processor design and the size of the machine 284 | cache on which the interpreter is run, \ref{fillers} may be more efficient. 285 | 286 | Finally, there is no reason why two methods should not be combined, and one used 287 | for numeric literals while the other is used for addresses. With numeric literals, 288 | the advantage of compactness is much greater, since most constants used in programming 289 | are small. Especially if method \ref{simple} is used for address literals, another 290 | method can usefully be used to deal with numeric literals, which are often negative. 291 | Alternatively, different opcodes could be used for positive and negative numbers. 292 | 293 | The author's preference is for method~\ref{fillers}. Assuming relative addressing 294 | is used, which seems sensible in an interpreter precisely so that the advantage 295 | of compactness can be gained, method~\ref{fillers} offers a significant increase 296 | in speed and compactness of code over na\"ive methods, but with minimal impact on 297 | the design and size of the interpreter. If the best method is required, the author 298 | suggests that both methods \ref{fillers} and~\ref{variable} be tested to determine 299 | which works better in practice. 300 | 301 | 302 | \section{Conclusion} 303 | 304 | Now that the dominance of eight-bit processors has ended in mainstream computing, 305 | it is time to reassess the best ways of building interpreters (and perhaps whether 306 | they ought to be built at all; this paper presupposes that they should be). With 307 | a little thought it is easy to find optimisations which are applicable to a wide 308 | range of modern architectures; a few have been discussed here. It is important 309 | to remember that while program size is not as important in RISC processor design 310 | as it used to be when address spaces were smaller and memory slower, it is still 311 | crucial for the software interpreter which cannot take advantage of hardware pipelining 312 | and parallel operation as machine code can. 313 | 314 | 315 | \section{Acknowledgements} 316 | 317 | Some of the ideas explored here were first pointed out to me by Martin Richards, 318 | who also read and criticised the paper. 319 | \end{document} 320 | -------------------------------------------------------------------------------- /doc/papers/tradeoffs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/doc/papers/tradeoffs.pdf -------------------------------------------------------------------------------- /doc/papers/tradeoffs.tex: -------------------------------------------------------------------------------- 1 | % 2 | % Tradeoffs in the implementation of the Beetle virtual machine 3 | % 4 | % Reuben Thomas 28/5-1/7/96 5 | % 6 | 7 | 8 | \documentclass{article} 9 | \usepackage{a4,newlfont,dcolumn,url,hevea} 10 | 11 | \frenchspacing 12 | \newcolumntype{d}[1]{D{#1}{#1}{-1}} 13 | \newcommand{\coldash}{\multicolumn{1}{c|}{-}} 14 | \setlength{\extrarowheight}{1pt} 15 | 16 | 17 | \title{Tradeoffs in the implementation of the\\Beetle virtual machine} 18 | \author{Reuben Thomas} 19 | \date{1st July 1996; revised 3rd June 2016} 20 | 21 | \begin{document} 22 | \maketitle 23 | 24 | 25 | \section{Introduction} 26 | 27 | Beetle is a virtual machine designed for running Forth compilers such as 28 | pForth~\cite{beetledis}. It has been implemented in ANSI C~\cite{beetledis} for 29 | portability, and, more recently, in ARM assembler. This paper describes the 30 | tradeoffs within and between the two implementations, and compares them to 31 | native Forth and C compilers. 32 | 33 | 34 | \section{The virtual machine} 35 | 36 | Beetle's computational model is a stack machine, and most of its instructions 37 | are zero-operand, taking their operands implicitly from the data stack. The 38 | only exceptions are branches, subroutine calls, and instructions to place 39 | numeric literals on the stack. There are about ninety instructions, most of 40 | which directly implement simple Forth words. A few others support specific 41 | control and data constructs. 42 | 43 | The instruction set is encoded as a byte stream grouped into words of four 44 | bytes. Instructions are fetched a word at a time, and the bytes shifted out, 45 | decoded and executed. This makes instruction fetch more efficient than for a 46 | pure byte-stream encoding on many machines, though it introduces the overhead 47 | of testing to see when a new instruction word must be fetched. Since the 48 | instruction accumulator is arithmetically right-shifted after each 49 | instruction has been decoded, this is achieved simply by making opcodes 00h 50 | and FFh represent an instruction fetch. 51 | 52 | Another consequence of grouping instructions into words is that branch 53 | destinations are word-aligned. Thus, gaps sometimes appear in the 54 | instruction stream where two control flows join; these are simply filled with 55 | 00h, so that when the gap is reached, execution proceeds immediately to the 56 | next instruction word. 57 | 58 | The only major problem caused by using a word-stream design is that endism, 59 | or endianness, reared its ugly head. The solution adopted was to declare that 60 | byte addressing is little-endian, but that it does not matter how the bytes 61 | are actually stored. Thus, the only penalty is that byte addresses must have 62 | their two least significant bits inverted on big-endian machines; there is no 63 | penalty for word addressing, which is used for the majority of operations. 64 | 65 | Address checking is optional in Beetle's design, because generally it is 66 | impossible to implement so that there is no time penalty when it is turned 67 | off, unless hardware checking is supported. Both implementations have 68 | optional address checking that can be configured on or off at compile time. 69 | 70 | 71 | \section{The implementations} 72 | 73 | A byte-stream-encoded stack machine is possibly the easiest sort of virtual 74 | machine to implement, and the change to a word stream only adds slight 75 | complication to the instruction fetch. The obvious implementation method, and 76 | that used in both implementations discussed here, is to switch on each 77 | instruction opcode to branch to the action routine for the instruction. 78 | 79 | \subsection{C Beetle} 80 | 81 | The main aim of the C implementation of Beetle was that it should be easily 82 | portable. To this end it was written in strict ANSI C, and implementation 83 | dependencies were carefully isolated. In this it was successful (to date it 84 | has been compiled on four different machine and operating system 85 | configurations), but it sacrifices speed. The only concession to efficiency 86 | was to have two versions of the interpretive loop, one for single stepping, 87 | and the other for continuous running. On the other hand, it was quick to 88 | write (the first version was completed in about three weeks, at a time when 89 | the specification was still changing), easy to debug, and served as a useful 90 | pattern for the other implementation. 91 | 92 | The C implementation was provided with a simple command-line user interface, 93 | including such facilities as single-stepping, disassembly and simple 94 | profiling. 95 | 96 | \subsection{ARM Beetle} 97 | 98 | One of the design aims of Beetle was that it should be possible to produce 99 | fast hand-coded versions for particular machines. Such a version was written 100 | for the ARM processor (running under Acorn RISC OS). A branch table was used 101 | for instruction dispatch, and the routine for decoding the next instruction 102 | was expanded inline after each action routine. The registers of the virtual 103 | machine, such as the program counter and stack pointers, were mapped on to 104 | machine registers, and the action routines were carefully hand-coded. In 105 | particular, instruction decode was coded in three instructions and 106 | instruction fetch in one. Some optimisations, such as caching stack elements, 107 | were expressly forbidden by Beetle's specification, so that different 108 | implementations would be indistinguishable to programs running on them. 109 | 110 | Address checking and single stepping were provided as independent optional 111 | extras. The single stepping mode also counts how many times each instruction 112 | is executed, to allow simple profiling. 113 | 114 | The hand-coded Beetle is a direct replacement for the equivalent functions in 115 | the C version. 116 | 117 | 118 | \section{Measurements} 119 | 120 | Benchmarks were run on both versions Beetle running in all operating modes 121 | (that is, with and without address checking, and, in the case of the ARM 122 | implementation, with without single-stepping and profiling). These consisted 123 | of Forth programs compiled by the pForth compiler. For comparison, the 124 | benchmarks were also run on a native-code version of pForth, and some of the 125 | benchmarks were translated into C. 126 | 127 | The main purpose of the benchmarks was to determine how closely interpretive 128 | performance approached compiled performance; other issues considered were the 129 | relative speeds of the virtual and real machines and the relative speed of 130 | na\"{\i}vely-compiled Forth and optimised C (no optimising Forth compiler was 131 | available; however it is safe to say that it is unlikely to have been faster 132 | than the C compiler used, in any case). 133 | 134 | All the benchmarks were run on an Acorn Risc PC 600, with a 30Mhz ARM610, in 135 | single-tasking mode. 136 | 137 | The benchmarks were timed, and the number of interpretive instructions 138 | executed was counted. The static size of the benchmark and interpreter code 139 | was also measured. 140 | 141 | \subsection{The benchmarks} 142 | 143 | Two main benchmarks were run, one I/O-bound, and one computation-intensive. 144 | The first was a suite of tests designed to show that a Forth compiler 145 | complies with the ANSI standard. It is essentially a long script which is 146 | read from storage as the tests progress. The second benchmark was a pair of 147 | implementations of Eratosthenes's sieve, counting, in this case, all the 148 | primes up to 800,000, three times. 149 | 150 | The code for the sieve benchmarks is given in appendix~\ref{code}; that for 151 | the ANSI test suite is long and unenlightening. 152 | 153 | The results of running the benchmarks are given in 154 | tables~\ref{bench1tab}--~\ref{countstab}. All measurements were taken by hand 155 | with a stopwatch, and averaged over several readings, which rarely differed 156 | by more than 0.1s. All results are given to two significant figures. 157 | 158 | When the number of instructions executed by Beetle were counted, only useful 159 | instructions were included; the instruction fetch instruction was omitted 160 | from the counts, and hence from the performance figures. 161 | 162 | \begin{table} 163 | \begin{center} 164 | \begin{latexonly} 165 | \begin{tabular}{|l|c|d{.}|} \hline 166 | \rule[-2mm]{0mm}{6mm}\bf System & \bf Time/s & \multicolumn{1}{c|}{\bf mips} 167 | \\ \hline 168 | Native ARM & 4.3 & \coldash \\ 169 | Native Beetle (no checks) & 5.6 & 1.1 \\ 170 | Native Beetle (address checks) & 8.0 & 0.76 \\ 171 | Native Beetle (single stepping) & 10 & 0.61 \\ 172 | Native Beetle (checking \& stepping) & 14 & 0.44 \\ 173 | C Beetle (checking off) & 16 & 0.38 \\ 174 | C Beetle (checking on) & 26 & 0.23 \\ \hline 175 | \end{tabular} 176 | \end{latexonly} 177 | \begin{htmlonly} 178 | \begin{tabular}{|l|c|l|} \hline 179 | \rule[-2mm]{0mm}{6mm}\bf System & \bf Time/s & \bf mips 180 | \\ \hline 181 | Native ARM & 4.3 & \coldash \\ 182 | Native Beetle (no checks) & 5.6 & 1.1 \\ 183 | Native Beetle (address checks) & 8.0 & 0.76 \\ 184 | Native Beetle (single stepping) & 10 & 0.61 \\ 185 | Native Beetle (checking \& stepping) & 14 & 0.44 \\ 186 | C Beetle (checking off) & 16 & 0.38 \\ 187 | C Beetle (checking on) & 26 & 0.23 \\ \hline 188 | \end{tabular} 189 | \end{htmlonly} 190 | \caption{\label{bench1tab}Timings for the ANSI test suite benchmark} 191 | \end{center} 192 | \end{table} 193 | 194 | \begin{table} 195 | \begin{center} 196 | \begin{latexonly} 197 | \begin{tabular}{|l|c|d{.}|} \hline 198 | \rule[-2mm]{0mm}{6mm}\bf System & \bf Time/s & \multicolumn{1}{c|}{\bf mips} 199 | \\ \hline 200 | Native ARM & 20 & \coldash \\ 201 | Native Beetle (no checks) & 35 & 1.9 \\ 202 | Native Beetle (address checks) & 54 & 1.3 \\ 203 | Native Beetle (single stepping) & 70 & 0.97 \\ 204 | Native Beetle (checking \& stepping) & 88 & 0.77 \\ 205 | C Beetle (checking off) & 130 & 0.52 \\ 206 | C Beetle (checking on) & 200 & 0.34 \\ 207 | GNU C (optimised) & 2.0 & \coldash \\ 208 | GNU C (unoptimised) & 6.5 & \coldash \\ \hline 209 | \end{tabular} 210 | \end{latexonly} 211 | \begin{htmlonly} 212 | \begin{tabular}{|l|c|l|} \hline 213 | \rule[-2mm]{0mm}{6mm}\bf System & \bf Time/s & \bf mips 214 | \\ \hline 215 | Native ARM & 20 & \coldash \\ 216 | Native Beetle (no checks) & 35 & 1.9 \\ 217 | Native Beetle (address checks) & 54 & 1.3 \\ 218 | Native Beetle (single stepping) & 70 & 0.97 \\ 219 | Native Beetle (checking \& stepping) & 88 & 0.77 \\ 220 | C Beetle (checking off) & 130 & 0.52 \\ 221 | C Beetle (checking on) & 200 & 0.34 \\ 222 | GNU C (optimised) & 2.0 & \coldash \\ 223 | GNU C (unoptimised) & 6.5 & \coldash \\ \hline 224 | \end{tabular} 225 | \end{htmlonly} 226 | \caption{\label{bench2tab}Timings for the primes benchmark (method 1)} 227 | \end{center} 228 | \end{table} 229 | 230 | \begin{table} 231 | \begin{center} 232 | \begin{latexonly} 233 | \begin{tabular}{|l|c|d{.}|} \hline 234 | \rule[-2mm]{0mm}{6mm}\bf System & \bf Time/s & \multicolumn{1}{c|}{\bf mips} 235 | \\ \hline 236 | Native ARM & 14 & \coldash \\ 237 | Native Beetle (no checks) & 20 & 1.8 \\ 238 | Native Beetle (address checks) & 31 & 1.2 \\ 239 | Native Beetle (single stepping) & 39 & 0.93 \\ 240 | Native Beetle (checking \& stepping) & 50 & 0.73 \\ 241 | C Beetle (checking off) & 75 & 0.49 \\ 242 | C Beetle (checking on) & 120 & 0.30 \\ 243 | GNU C (optimised) & 1.9 & \coldash \\ 244 | GNU C (unoptimised) & 6.4 & \coldash \\ \hline 245 | \end{tabular} 246 | \end{latexonly} 247 | \begin{htmlonly} 248 | \begin{tabular}{|l|c|l|} \hline 249 | \rule[-2mm]{0mm}{6mm}\bf System & \bf Time/s & \bf mips 250 | \\ \hline 251 | Native ARM & 14 & \coldash \\ 252 | Native Beetle (no checks) & 20 & 1.8 \\ 253 | Native Beetle (address checks) & 31 & 1.2 \\ 254 | Native Beetle (single stepping) & 39 & 0.93 \\ 255 | Native Beetle (checking \& stepping) & 50 & 0.73 \\ 256 | C Beetle (checking off) & 75 & 0.49 \\ 257 | C Beetle (checking on) & 120 & 0.30 \\ 258 | GNU C (optimised) & 1.9 & \coldash \\ 259 | GNU C (unoptimised) & 6.4 & \coldash \\ \hline 260 | \end{tabular} 261 | \end{htmlonly} 262 | \caption{\label{bench3tab}Timings for the primes benchmark (method 2)} 263 | \end{center} 264 | \end{table} 265 | 266 | \begin{table} 267 | \begin{center} 268 | \begin{latexonly} 269 | \begin{tabular}{|l|d{.}|} \hline 270 | \rule[-2mm]{0mm}{6mm}\bf Benchmark & \multicolumn{1}{c|}{\bf Instructions / 271 | {\boldmath$ 10^6$}} \\ \hline 272 | ANSI test suite & 6.1 \\ 273 | Primes method 1 & 67.8 \\ 274 | Primes method 2 & 36.4 \\ \hline 275 | \end{tabular} 276 | \end{latexonly} 277 | \begin{htmlonly} 278 | \begin{tabular}{|l|l|} \hline 279 | \rule[-2mm]{0mm}{6mm}\bf Benchmark & \bf Instructions / {\boldmath$ 10^6$} \\ \hline 280 | ANSI test suite & 6.1 \\ 281 | Primes method 1 & 67.8 \\ 282 | Primes method 2 & 36.4 \\ \hline 283 | \end{tabular} 284 | \end{htmlonly} 285 | \caption{\label{countstab}Numbers of Beetle instructions executed in each 286 | benchmark} 287 | \end{center} 288 | \end{table} 289 | 290 | \subsection{Code size} 291 | 292 | It is also interesting to compare static measurements of the different 293 | implementations of Beetle. Table~\ref{sizetab} shows their sizes. The sizes 294 | of the C versions are those of the object modules, and exclude the C runtime 295 | system and other support code; the sizes of the native versions includes all 296 | support code. 297 | 298 | \begin{table} 299 | \begin{center} 300 | \begin{latexonly} 301 | \begin{tabular}{|l|d{,}|} \hline 302 | \rule[-2mm]{0mm}{6mm}\bf Implementation & \multicolumn{1}{c|}{\bf Size/bytes} 303 | \\ \hline 304 | Native Beetle (no checks) & 3,920\\ 305 | Native Beetle (address checks) & 6,692\\ 306 | Native Beetle (single stepping) & 8,328\\ 307 | Native Beetle (checking \& stepping) & 11,100\\ 308 | C Beetle (checking off) & 7,144 \\ 309 | C Beetle (checking on) & 13,864 \\ \hline 310 | \end{tabular} 311 | \end{latexonly} 312 | \begin{htmlonly} 313 | \begin{tabular}{|l|l|} \hline 314 | \rule[-2mm]{0mm}{6mm}\bf Implementation & \bf Size/bytes 315 | \\ \hline 316 | Native Beetle (no checks) & 3,920\\ 317 | Native Beetle (address checks) & 6,692\\ 318 | Native Beetle (single stepping) & 8,328\\ 319 | Native Beetle (checking \& stepping) & 11,100\\ 320 | C Beetle (checking off) & 7,144 \\ 321 | C Beetle (checking on) & 13,864 \\ \hline 322 | \end{tabular} 323 | \end{htmlonly} 324 | \caption{\label{sizetab}Sizes of different implementations of Beetle} 325 | \end{center} 326 | \end{table} 327 | 328 | The benchmarks themselves were also measured. The ANSI test suite compiled 329 | incrementally as it ran into 5,372 bytes, and the two primes tests compiled 330 | into 356 bytes in total, with another 400,000 bytes used for the main array. 331 | 332 | 333 | \section{Analysis} 334 | 335 | The analysis is split into three parts; the first compares the two 336 | implementations of the interpreter, the second compares the hand-coded 337 | interpreter with the native pForth compiler, and the third makes comparisons 338 | between all three, and with natively compiled and optimised C. 339 | 340 | Many of the comparisons refer to table~\ref{speedtab}, which gives the mean 341 | speed of each implementation relative to the fastest hand-coded version of 342 | Beetle; the tests run in C are also included. 343 | 344 | \begin{table} 345 | \begin{center} 346 | \begin{latexonly} 347 | \begin{tabular}{|l|d{.}|} \hline 348 | \rule[-2mm]{0mm}{6mm}\bf Implementation & \multicolumn{1}{c|}{\bf Relative 349 | speed} \\ \hline 350 | Native ARM & 1.5 \\ 351 | Native Beetle (no checks) & 1.0 \\ 352 | Native Beetle (address checks) & 0.66 \\ 353 | Native Beetle (single stepping) & 0.52 \\ 354 | Native Beetle (checking \& stepping) & 0.40 \\ 355 | C Beetle (checking off) & 0.30 \\ 356 | C Beetle (checking on) & 0.19 \\ 357 | GNU C (optimised) & \multicolumn{1}{c|}{14} \\ 358 | GNU C (unoptimised) & 4.3 \\ \hline 359 | \end{tabular} 360 | \end{latexonly} 361 | \begin{htmlonly} 362 | \begin{tabular}{|l|l|} \hline 363 | \rule[-2mm]{0mm}{6mm}\bf Implementation & \bf Relative speed \\ \hline 364 | Native ARM & 1.5 \\ 365 | Native Beetle (no checks) & 1.0 \\ 366 | Native Beetle (address checks) & 0.66 \\ 367 | Native Beetle (single stepping) & 0.52 \\ 368 | Native Beetle (checking \& stepping) & 0.40 \\ 369 | C Beetle (checking off) & 0.30 \\ 370 | C Beetle (checking on) & 0.19 \\ 371 | GNU C (optimised) & \multicolumn{1}{c|}{14} \\ 372 | GNU C (unoptimised) & 4.3 \\ \hline 373 | \end{tabular} 374 | \end{htmlonly} 375 | \caption{\label{speedtab}Relative speeds} 376 | \end{center} 377 | \end{table} 378 | 379 | \subsection{C Beetle \textit{versus} ARM Beetle} 380 | 381 | There is no doubt that the hand-coded version of Beetle for the ARM processor 382 | gave a marked speed improvement: the slowest native version is 1.3 times 383 | quicker than the fastest C version; the fastest native Beetle is over five 384 | times faster than C Beetle with address checks. The difference between 385 | corresponding versions is a factor of about 3.4. 386 | 387 | The only advantage that the native version of Beetle has over the ARM version 388 | is that it is easily portable; it required between thirty minutes and a day's 389 | work to port it to each of three different systems, while it took a week to 390 | write and debug the ARM version. Though a week is far less time than it took 391 | to write the C version originally, and not long for such performance gains, 392 | the present author did have the benefit of a thorough knowledge of Beetle, 393 | the ARM processor and the RISC OS environment; further hand-coded versions 394 | would certainly take longer to produce. 395 | 396 | The code size of an interpreter is important insofar as the interpreter will 397 | run considerably faster if it can reside mainly in the processor's cache. The 398 | ARM610 has a 4kb cache, so this goal is only achieved by the smallest version 399 | of the native implementation; however, even within the interpreter there is 400 | locality of code, so that even the larger C version will have a reasonable 401 | cache hit rate. Typical high-performance microprocessors have much larger 402 | caches, in any case. 403 | 404 | According to these considerations, it seems that hand-coding Beetle was a 405 | worthwhile effort on the machine in question; on higher performance machines 406 | the extra effort is only worthwhile if the extra performance is required; it 407 | is also not clear how much the performance would be improved on architectures 408 | which lend themselves less readily to hand-coding than the delightful ARM. 409 | 410 | \subsection{ARM Beetle \textit{versus} native pForth} 411 | 412 | One of the most interesting results of the tests is that pForth runs only 1.5 413 | times slower on the ARM version of Beetle than natively. This suggests that 414 | there is little point producing native versions of pForth at all, and that a 415 | good implementation of Beetle produces near-optimal execution of pForth. 416 | 417 | An important point is that pForth is a na\"{\i}ve compiler, which performs no 418 | optimisation. Since Beetle is designed specifically to execute compiled 419 | Forth, a na\"{\i}ve compiler will produce good code for it; indeed, it is not 420 | possible to perform much optimisation, as Forth does not have conventional 421 | variables or scoping, and Beetle has no general purpose registers. 422 | 423 | \subsection{General comparisons} 424 | 425 | While the ARM implementation of Beetle compares well with a native Forth 426 | compiler, the comparison with optimised C shows that there is still a long 427 | way to go to obtain high performance. A factor of 14 is rather disheartening 428 | in one sense, but in another it is irrelevant, as pForth is not designed to 429 | produce fast code, but to provide a simple, comprehensible compiler, suitable 430 | for teaching the principles of Forth compilation. Forth, too, does not aim to 431 | give maximum speed, although Forth compilers usually provide assemblers so 432 | that time-critical code can be hand coded; often, only a small portion of a 433 | program needs to be coded to obtain C-like performance. Optimising Forth 434 | compilers also exist. 435 | 436 | It is also worth noting that the test system is a slow computer. Even the C 437 | version of Beetle with address checking on runs acceptably fast on typical 438 | large multi-user systems; indeed it runs at much the same sort of speed as 439 | the hand-coded ARM Beetle. Native versions for such powerful systems would 440 | run even faster, and provide more than adequate power for typical program 441 | development. 442 | 443 | 444 | \section{Conclusions} 445 | 446 | The implementation of Beetle in ARM assembler demonstrates that resonable 447 | performance can be obtained from interpreters. Interpreters are also quick to 448 | implement, and easily portable, not much less so when hand-coded than when 449 | implemented in a portable high-level language. 450 | 451 | Code compiled by a na\"{\i}ve compiler can execute at near-optimal speed when 452 | interpreted, provided that the interpreter is designed to support the 453 | language being compiled. There seems to be little point implementing 454 | simple-minded compilers in native code. It may even be a disadvantage for an 455 | interpreted compiler to perform optimisation, as this will increase 456 | compilation time, and in an interpretive environment, particularly one such 457 | as Forth, which makes use of incremental compilation, this will interfere 458 | with the rapid development cycle. 459 | 460 | Even when implemented portably in a high-level language, interpreters can run 461 | at a reasonable speed; although they cannot use machine-dependent tricks, 462 | they can take advantage of optimising compilers to boost their speed. 463 | 464 | 465 | \bibliographystyle{plain} 466 | \bibliography{vm,rrt} 467 | 468 | \newpage 469 | \begin{appendix} 470 | \section{Code for the sieve benchmarks} 471 | \label{code} 472 | 473 | These benchmarks are adapted from~\cite{forthsieve}. 474 | 475 | \begin{verbatim} 476 | 400000 CONSTANT SIZE 477 | VARIABLE FLAGS SIZE ALLOT 478 | 479 | : DO-PRIME ( method 1 ) 480 | FLAGS SIZE 1 FILL 481 | 0 482 | SIZE 0 DO 483 | FLAGS I + C@ IF 484 | I 2* 3 + DUP I 485 | BEGIN DUP SIZE < WHILE 486 | 0 OVER FLAGS + C! OVER + 487 | REPEAT 488 | DROP DROP 1+ 489 | THEN 490 | LOOP 491 | . ." primes " ; 492 | \end{verbatim} 493 | 494 | \begin{verbatim} 495 | : DO-PRIME-HI ( method 2 ) 496 | FLAGS SIZE 1 FILL 497 | 0 498 | SIZE 0 DO 499 | I FLAGS + C@ IF 500 | I 2* 3 + DUP I + SIZE < IF 501 | SIZE FLAGS + OVER I + FLAGS + DO 502 | 0 I C! 503 | DUP +LOOP 504 | THEN 505 | DROP 1+ 506 | THEN 507 | LOOP 508 | . ." primes " ; 509 | \end{verbatim} 510 | \end{appendix} 511 | 512 | 513 | \end{document} 514 | -------------------------------------------------------------------------------- /doc/primes.c: -------------------------------------------------------------------------------- 1 | /* Translation of Forth primes benchmark (adapted from Modula-2 version) 2 | For comparison against pForth 3 | Reuben Thomas 28/5/96 4 | */ 5 | 6 | #include 7 | 8 | #define size 400000 9 | char isprime[size]; 10 | 11 | void do_prime() 12 | { 13 | int i, j, k, prime, counter; 14 | 15 | counter = 0; 16 | for (j = 0; j < size; j++) { 17 | isprime[j] = 1; 18 | } 19 | 20 | for (j = 0; j < size; j++) { 21 | if (isprime[j]) { 22 | prime = 2*j+3; 23 | for (k = j+prime; k < size; k += prime) isprime[k] = 0; 24 | counter++; 25 | } 26 | } 27 | 28 | printf("%d primes ", counter); 29 | } 30 | 31 | void do_prime_hi() 32 | { 33 | int i, j, k, prime, counter; 34 | 35 | counter = 0; 36 | for (j = 0; j < size; j++) { 37 | isprime[j] = 1; 38 | } 39 | 40 | for (j = 0; j < size; j++) { 41 | if (isprime[j]) { 42 | prime = 2*j+3; 43 | k = j+prime; 44 | if (k+j < size) for (; k < size; k += prime) isprime[k] = 0; 45 | counter++; 46 | } 47 | } 48 | 49 | printf("%d primes ", counter); 50 | } 51 | 52 | int main(void) 53 | { 54 | int i; 55 | 56 | for (i = 0; i < 3; i++) do_prime(); putchar('\n'); 57 | getchar(); 58 | for (i = 0; i < 3; i++) do_prime_hi(); putchar('\n'); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /doc/shell.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/doc/shell.pdf -------------------------------------------------------------------------------- /doc/shell.tex.in: -------------------------------------------------------------------------------- 1 | % 2 | % Documentation for Beetle shell 3 | % 4 | % Reuben Thomas 5 | % 6 | % Started 27/2-8/5/95 7 | % 8 | 9 | \documentclass{article} 10 | \usepackage[british]{babel} 11 | \usepackage[utf8x]{inputenc} 12 | \usepackage{a4wide,newpxtext,booktabs,url,xr} 13 | 14 | % Alter some default parameters for general typesetting 15 | 16 | \frenchspacing 17 | 18 | % Allow external cross-references 19 | \externaldocument{beetle} 20 | \externaldocument{cbeetle} 21 | 22 | % Macros 23 | 24 | % Put an object in angle brackets for syntax descriptions 25 | 26 | \newcommand{\angb}[1]{$\langle #1\rangle$} 27 | 28 | 29 | \title{A simple shell for the\\Beetle virtual machine\\version @VERSION@} 30 | \author{Reuben Thomas} 31 | \date{26th September 2021} 32 | 33 | \begin{document} 34 | \maketitle 35 | 36 | 37 | \section{Introduction} 38 | 39 | This is the manual for the shell for C Beetle~\cite{cbeetle}, which provides access to Beetle's registers, allows the stacks to be displayed, and provides assembly, disassembly and single-stepping. 40 | 41 | There are two main ways to access the shell: either start {\tt beetle} with no arguments, or, when passing an object file on the command line, give the {\tt -\/-debug} option, which causes the shell to be entered on exception (interpreted as a negative value being passed to {\tt HALT}). The shell can also be used in batch mode, by supplying a file of commands with the {\tt -\/-commands} option; in this case, the interactive debugger is entered once the given commands have been executed, if standard input is a terminal. Refer to the man page \textbf{beetle}(1) or {\tt beetle -\/-help} for more details and options. 42 | 43 | 44 | \section{Initialisation} 45 | \label{uifaceinit} 46 | 47 | When the virtual machine is started, an embedded Beetle is created. The registers are initialised as described in~\cite[section~\ref{usingcalls}]{cbeetle}; additionally, {\tt 'THROW} is set to \$0, and {\tt I} is uninitialised. {\tt A} is set to zero: this has the effect that when a {\tt STEP} or {\tt RUN} command is given, a {\tt NEXT} instruction 48 | will be performed. The main memory is zeroed. 49 | 50 | 51 | \section{Commands} 52 | \label{commands} 53 | 54 | The shell is command-driven. All commands and register names may be 55 | abbreviated to their first few letters; where ambiguities are resolved with a set order of precedence, aimed at giving the most 56 | commonly used commands the shortest minimum abbreviations, and commands 57 | take precedence over registers. 58 | Alternatively, if you have {\tt beetlei} (which uses {\tt rlwrap}), you can use Tab-completion (press the Tab key to show possible commands and instructions starting with the letters you have typed so far). 59 | All commands are case-insensitive. 60 | 61 | The following registers are renamed for ease of typing: 62 | % FIXME: Accept original names too 63 | 64 | \begin{center} 65 | \begin{tabular}{cc} \toprule 66 | \bf Register & \bf Name in shell \\ \midrule 67 | {\tt 'THROW} & {\tt THROW} \\ 68 | {\tt 'BAD} & {\tt BAD} \\ 69 | {\tt -ADDRESS} & {\tt NOT\_ADDRESS} \\ \bottomrule 70 | \end{tabular} 71 | \end{center} 72 | 73 | If an unrecognised command is given, or the command has too few arguments, or 74 | they are badly formed, an error message is displayed. Command lines 75 | containing extraneous characters after a valid command are generally 76 | accepted, and the extra characters ignored. 77 | 78 | While in interactive mode, the previous command may be repeated by entering an empty command (just press ``RETURN'' or ``ENTER''). 79 | 80 | Numbers are all integral, and may be given in either decimal or hexadecimal 81 | (which must be preceded by ``\$''), with an optional minus 82 | sign. 83 | 84 | For some arguments a machine instruction may also be used, preceded by ``{\tt 85 | O}'', for opcode. The value of a machine instruction is its opcode. Opcodes 86 | are byte-wide values; when used as a cell, the most significant three bytes 87 | are set to zero. 88 | 89 | The syntax of the commands is shown below; literal text such as command names 90 | and other characters are shown in {\tt Typewriter font}; meta-parameters such 91 | as numbers are shown in angle brackets, thus: \angb{number}. Square brackets enclose optional tokens. 92 | 93 | There are three types of numeric meta-parameter: \angb{number}, which is any 94 | number; \angb{address}, which is a valid address (see~\cite[section~\ref{exceptions}]{beetle}); 95 | and \angb{value}, which is a number or an opcode. 96 | 97 | The command {\tt INFO} displays the contents of the stacks, and the contents 98 | of {\tt EP}, {\tt I} and {\tt A}, useful when following the execution of a 99 | program. 100 | 101 | \subsection{Comments} 102 | 103 | The string {\tt //} starts a comment, which runs to the end of the input line, and is ignored. 104 | 105 | \subsection{Registers} 106 | 107 | Beetle's registers may be displayed by typing their name. The 108 | registers may also (where appropriate) be assigned to using the syntax 109 | 110 | \begin{center}\angb{register} $=$ \angb{value}\end{center} 111 | 112 | \noindent where \angb{value} is in the form given in section~\ref{commands}. An error 113 | message is displayed if an attempt is made to assign to a register such as 114 | {\tt CHECKED}, which cannot be assigned to, or to assign an unaligned or out 115 | of range address to a register which must hold an aligned address, such as 116 | {\tt SP}. The {\tt FROM} command (see section~\ref{from}) should be used in 117 | preference to assigning to {\tt EP}. 118 | 119 | Two additional pseudo-registers are provided by the shell: they are 120 | called {\tt S0} and {\tt R0}, and are the address of the base of the data and 121 | return stacks respectively. They are set to the initial values of {\tt RP} 122 | and {\tt SP}, and are provided so that they can be changed if the stacks are 123 | moved, so the stack display commands will still work correctly. 124 | 125 | \subsection{Stacks} 126 | 127 | The stacks may be manipulated crudely using the registers {\tt SP} and {\tt 128 | RP} but it is usually more convenient to use the commands 129 | 130 | \begin{center} 131 | {\tt TOD} \angb{number}\\ 132 | {\tt DFROM} 133 | \end{center} 134 | 135 | \noindent which respectively push a number on to the data stack and pop one, displaying 136 | it, and 137 | 138 | \begin{center} 139 | {\tt TOR} \angb{number}\\ 140 | {\tt RFROM} 141 | \end{center} 142 | 143 | \noindent which do the same for the return stack. 144 | 145 | If a stack underflows, or the base pointer or top of stack pointer is out of 146 | range or unaligned, an appropriate error message is displayed. 147 | 148 | \subsection{Code and data} 149 | \label{codeanddata} 150 | 151 | \begin{center} 152 | \angb{opcode} 153 | {\tt LITERAL} \angb{number} 154 | {\tt BLITERAL} \angb{number} 155 | {\tt ILITERAL} \angb{number} 156 | {\tt PLITERAL} \angb{number} 157 | \end{center} 158 | 159 | Code and literal data values may be directly assembled into memory. Assembly starts at the last value explicitly assigned to {\tt EP} in the shell, defaulting to \$0 whenever Beetle is initialised (see section~\ref{uifaceinit}). An opcode or byte literal ({\tt BLITERAL}) takes one byte, and a cell literal ({\tt LITERAL}) is stored in the next whole cell. An inline literal ({\tt ILITERAL}) is stored in the remainder of the current cell, if it fits; otherwise an error is given. {\tt PLITERAL} assembles the instructions required to push the given native pointer on to the stack in the form required for the {\tt LINK} instruction. 160 | 161 | Each cell is always filled before moving on to the next available cell, so for example the sequence 162 | 163 | \begin{center} 164 | {\tt (LITERAL)} {\tt ,} {\tt \$12345678} {\tt +} 165 | \end{center} 166 | 167 | \noindent assembles the two instructions {\tt (LITERAL)} and {\tt +} in consecutive bytes, and then the cell \$12345678. 168 | 169 | \subsection{Memory} 170 | 171 | The contents of an address may be displayed by giving the address as a 172 | command. If the address is cell-aligned the whole cell is displayed, 173 | otherwise the byte at that address is shown. 174 | 175 | A larger section of memory may be displayed with the command {\tt DUMP}, 176 | which may be used in the two forms 177 | 178 | \begin{center} 179 | {\tt DUMP} [\angb{address} [{\tt +} \angb{number}]]\\ 180 | {\tt DUMP} \angb{address_1} [\angb{address_2}] 181 | \end{center} 182 | 183 | \noindent where the first displays \angb{number} bytes (or $256$ if the number is omitted) starting at address 184 | \angb{address} (or $64$ bytes before {\tt EP} if the address is omitted, or $0$ if that would be negative), and the second displays memory from address \angb{address_1} 185 | up to, but not including, address \angb{address_2}. An error message is 186 | displayed if the start address is less than or equal to the end address or if 187 | either address is out of range. 188 | 189 | A command of the form 190 | 191 | \begin{center}\angb{address} $=$ \angb{value}\end{center} 192 | 193 | \noindent assigns the value \angb{value} to the address \angb{address}. If the address 194 | is not cell-aligned, the value must fit in a byte, and only that byte is 195 | assigned to. When assigning to an aligned memory location, a whole cell is 196 | assigned unless the number given fits in a byte, and is given using the 197 | minimum number of digits required. This should be noted the other way around: 198 | to assign a byte-sized significand to a cell, it should be padded with a 199 | leading zero. 200 | 201 | \subsection{Execution} 202 | 203 | The command {\tt STEP} may be used to single-step through a program. It has 204 | three forms: 205 | 206 | \begin{center} 207 | {\tt STEP} [\angb{number}]\\ 208 | {\tt STEP TO} \angb{address} 209 | \end{center} 210 | 211 | With no argument, {\tt STEP} executes one instruction. Given a number, {\tt 212 | STEP} executes \angb{number} instructions. {\tt STEP TO} executes 213 | instructions until {\tt EP} is equal to \angb{address}. 214 | 215 | The command {\tt TRACE}, has the same syntax as {\tt STEP}, and performs the 216 | same function; in addition, it performs the action of the {\tt INFO} 217 | command after each bForth instruction is executed. 218 | 219 | The command {\tt RUN} allows Beetle to execute until it reaches a {\tt HALT} 220 | instruction, if ever. The code passed to {\tt HALT} is then displayed. The 221 | code is also displayed if a {\tt HALT} instruction is ever executed during a 222 | {\tt STEP} command. 223 | 224 | \label{from} 225 | The command {\tt FROM} sets the point of execution: 226 | 227 | \begin{center} 228 | {\tt FROM} [\angb{address}] 229 | \end{center} 230 | 231 | With no argument, {\tt FROM} performs the function of Beetle's {\tt NEXT} 232 | instruction, that is, it loads {\tt A} from the cell pointed to by {\tt EP}, 233 | and adds four to {\tt EP}. With an argument, {\tt FROM} sets {\tt EP} to 234 | \angb{address}, and then performs the function of {\tt NEXT}. {\tt FROM} 235 | should be used in preference to assigning directly to {\tt EP}. 236 | 237 | The command {\tt DISASSEMBLE} disassembles bForth code. It may be used in the 238 | two forms 239 | 240 | \begin{center} 241 | {\tt DISASSEMBLE} [\angb{address} [{\tt +} \angb{number}]]\\ 242 | {\tt DISASSEMBLE} \angb{address_1} \angb{address_2} 243 | \end{center} 244 | 245 | \noindent where the first disassembles \angb{number} bytes (or $64$ if the number is omitted) starting at address 246 | \angb{address} (or $16$ bytes before {\tt EP}, or $0$ if that would be negative, if the address is omitted), and the second from address \angb{address_1} up to, but not 247 | including, address \angb{address_2}. The addresses must be cell-aligned, and 248 | the number of bytes must be a multiple of four. An error message is displayed 249 | if the start address is less than or equal to the end address, or if either 250 | the address or number of bytes is not aligned or is out of range. 251 | 252 | The command {\tt COUNTS} displays the number of times 253 | that each Beetle instruction has been executed during {\tt STEP} or {\tt TRACE} execution since the last initialisation 254 | (including loads). 255 | 256 | \subsection{Object modules} 257 | 258 | The command 259 | 260 | \label{uifaceload} 261 | \begin{center}{\tt LOAD} \angb{file} [\angb{address}]\end{center} 262 | 263 | \noindent loads the object 264 | module in file \angb{file} into memory at address \angb{address} (or address \$0 if the argument is omitted). The address 265 | must be cell-aligned; if it is not, or if the module would not fit in memory 266 | at the address given, or there is some filing error, an error message is 267 | displayed. 268 | 269 | The command {\tt SAVE} saves an object module. It has the two forms 270 | 271 | \begin{center} 272 | {\tt SAVE} \angb{file} \angb{address} {\tt +} \angb{number}\\ 273 | {\tt SAVE} \angb{file} \angb{address_1} \angb{address_2} 274 | \end{center} 275 | 276 | \noindent where the first saves \angb{number} bytes starting at address \angb{address}, 277 | and the second saves from address \angb{address_1} up to, but not including, 278 | address \angb{address_2}. The addresses must be cell-aligned, and the number 279 | of bytes must be a multiple of four. An error message is displayed if the 280 | start address is less than or equal to the end address, or if either the 281 | address or number of bytes is not aligned or out of range. 282 | 283 | The module is saved to the file \angb{file}. An error message is displayed if 284 | there is some filing error, but no warning is given if a file of that name 285 | already exists; it is overwritten. 286 | 287 | \subsection{Exiting} 288 | 289 | The command {\tt QUIT} exits Beetle. No warning is given. 290 | 291 | 292 | \bibliographystyle{plain} 293 | \bibliography{rrt} 294 | 295 | 296 | \end{document} -------------------------------------------------------------------------------- /m4/.gitignore: -------------------------------------------------------------------------------- 1 | /00gnulib.m4 2 | /gnulib-cache.m4 3 | /gnulib-common.m4 4 | /gnulib-comp.m4 5 | /gnulib-tool.m4 6 | /onceonly.m4 7 | /absolute-header.m4 8 | /extensions.m4 9 | /extern-inline.m4 10 | /getopt.m4 11 | /include_next.m4 12 | /nocrash.m4 13 | /off_t.m4 14 | /ssize_t.m4 15 | /stddef_h.m4 16 | /sys_types_h.m4 17 | /unistd_h.m4 18 | /warn-on-use.m4 19 | /wchar_t.m4 20 | /manywarnings.m4 21 | /warnings.m4 22 | /alloca.m4 23 | /errno_h.m4 24 | /error.m4 25 | /exponentd.m4 26 | /float_h.m4 27 | /intmax_t.m4 28 | /inttypes_h.m4 29 | /longlong.m4 30 | /math_h.m4 31 | /memchr.m4 32 | /mmap-anon.m4 33 | /msvc-inval.m4 34 | /msvc-nothrow.m4 35 | /multiarch.m4 36 | /printf.m4 37 | /size_max.m4 38 | /stdarg.m4 39 | /stdint.m4 40 | /stdint_h.m4 41 | /stdio_h.m4 42 | /stdlib_h.m4 43 | /strerror.m4 44 | /string_h.m4 45 | /sys_socket_h.m4 46 | /vasnprintf.m4 47 | /vasprintf.m4 48 | /wchar_h.m4 49 | /wint_t.m4 50 | /xalloc.m4 51 | /xsize.m4 52 | /xvasprintf.m4 53 | /gnu-make.m4 54 | /libtool.m4 55 | /ltoptions.m4 56 | /ltsugar.m4 57 | /ltversion.m4 58 | /lt~obsolete.m4 59 | /fseeko.m4 60 | /fstat.m4 61 | /ftell.m4 62 | /ftello.m4 63 | /gettimeofday.m4 64 | /largefile.m4 65 | /lseek.m4 66 | /sys_stat_h.m4 67 | /sys_time_h.m4 68 | /time_h.m4 69 | /dirname.m4 70 | /double-slash-root.m4 71 | /getprogname.m4 72 | /limits-h.m4 73 | /malloc.m4 74 | /manywarnings-c++.m4 75 | /pathmax.m4 76 | /stat-time.m4 77 | /stdbool.m4 78 | /termios_h.m4 79 | /sys_wait_h.m4 80 | /close.m4 81 | /dup2.m4 82 | /fcntl-o.m4 83 | /fcntl.m4 84 | /fcntl_h.m4 85 | /getdtablesize.m4 86 | /fdatasync.m4 87 | /fsync.m4 88 | /minmax.m4 89 | /__inline.m4 90 | /builtin-expect.m4 91 | /closedir.m4 92 | /codeset.m4 93 | /d-type.m4 94 | /dirent_h.m4 95 | /dirfd.m4 96 | /eealloc.m4 97 | /flexmember.m4 98 | /fnmatch.m4 99 | /getlogin.m4 100 | /getlogin_r.m4 101 | /glob.m4 102 | /hard-locale.m4 103 | /localcharset.m4 104 | /locale-fr.m4 105 | /locale-ja.m4 106 | /locale-zh.m4 107 | /lstat.m4 108 | /malloca.m4 109 | /mbrtowc.m4 110 | /mbsinit.m4 111 | /mbsrtowcs.m4 112 | /mbstate_t.m4 113 | /mempcpy.m4 114 | /opendir.m4 115 | /readdir.m4 116 | /stat.m4 117 | /std-gnu11.m4 118 | /wctype_h.m4 119 | /configmake.m4 120 | /glibc21.m4 121 | /strndup.m4 122 | /strnlen.m4 123 | /xstrndup.m4 124 | /fnmatch_h.m4 125 | /glob_h.m4 126 | /getdelim.m4 127 | /getline.m4 128 | /valgrind-tests.m4 129 | /assert_h.m4 130 | /btowc.m4 131 | /build-to-host.m4 132 | /c-bool.m4 133 | /c32rtomb.m4 134 | /calloc.m4 135 | /chdir-long.m4 136 | /ctype_h.m4 137 | /error_h.m4 138 | /extensions-aix.m4 139 | /fchdir.m4 140 | /filenamecat.m4 141 | /free.m4 142 | /fstatat.m4 143 | /getcwd.m4 144 | /gnulib-i18n.m4 145 | /inline.m4 146 | /inttypes.m4 147 | /isblank.m4 148 | /iswblank.m4 149 | /iswctype.m4 150 | /iswdigit.m4 151 | /iswpunct.m4 152 | /iswxdigit.m4 153 | /libunistring-base.m4 154 | /locale-en.m4 155 | /locale_h.m4 156 | /mbrtoc32.m4 157 | /mbtowc.m4 158 | /memrchr.m4 159 | /mode_t.m4 160 | /musl.m4 161 | /off64_t.m4 162 | /open-cloexec.m4 163 | /open-slash.m4 164 | /open.m4 165 | /openat.m4 166 | /pid_t.m4 167 | /realloc.m4 168 | /reallocarray.m4 169 | /save-cwd.m4 170 | /setlocale_null.m4 171 | /strdup.m4 172 | /strerrorname_np.m4 173 | /sys_cdefs_h.m4 174 | /threadlib.m4 175 | /uchar_h.m4 176 | /unicase_h.m4 177 | /unictype_h.m4 178 | /uninorm_h.m4 179 | /vararrays.m4 180 | /visibility.m4 181 | /wctype.m4 182 | /wmemchr.m4 183 | /wmempcpy.m4 184 | /zzgnulib.m4 185 | -------------------------------------------------------------------------------- /m4/ax_cc_maxopt.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_cc_maxopt.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CC_MAXOPT 8 | # 9 | # DESCRIPTION 10 | # 11 | # Try to turn on "good" C optimization flags for various compilers and 12 | # architectures, for some definition of "good". (In our case, good for 13 | # FFTW and hopefully for other scientific codes. Modify as needed.) 14 | # 15 | # The user can override the flags by setting the CFLAGS environment 16 | # variable. The user can also specify --enable-portable-binary in order to 17 | # disable any optimization flags that might result in a binary that only 18 | # runs on the host architecture. 19 | # 20 | # Note also that the flags assume that ANSI C aliasing rules are followed 21 | # by the code (e.g. for gcc's -fstrict-aliasing), and that floating-point 22 | # computations can be re-ordered as needed. 23 | # 24 | # Requires macros: AX_CHECK_COMPILE_FLAG, AX_COMPILER_VENDOR, 25 | # AX_GCC_ARCHFLAG, AX_GCC_X86_CPUID. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Steven G. Johnson 30 | # Copyright (c) 2008 Matteo Frigo 31 | # 32 | # This program is free software: you can redistribute it and/or modify it 33 | # under the terms of the GNU General Public License as published by the 34 | # Free Software Foundation, either version 3 of the License, or (at your 35 | # option) any later version. 36 | # 37 | # This program is distributed in the hope that it will be useful, but 38 | # WITHOUT ANY WARRANTY; without even the implied warranty of 39 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 40 | # Public License for more details. 41 | # 42 | # You should have received a copy of the GNU General Public License along 43 | # with this program. If not, see . 44 | # 45 | # As a special exception, the respective Autoconf Macro's copyright owner 46 | # gives unlimited permission to copy, distribute and modify the configure 47 | # scripts that are the output of Autoconf when processing the Macro. You 48 | # need not follow the terms of the GNU General Public License when using 49 | # or distributing such scripts, even though portions of the text of the 50 | # Macro appear in them. The GNU General Public License (GPL) does govern 51 | # all other use of the material that constitutes the Autoconf Macro. 52 | # 53 | # This special exception to the GPL applies to versions of the Autoconf 54 | # Macro released by the Autoconf Archive. When you make and distribute a 55 | # modified version of the Autoconf Macro, you may extend this special 56 | # exception to the GPL to apply to your modified version as well. 57 | 58 | #serial 17 59 | 60 | AC_DEFUN([AX_CC_MAXOPT], 61 | [ 62 | AC_REQUIRE([AC_PROG_CC]) 63 | AC_REQUIRE([AX_COMPILER_VENDOR]) 64 | AC_REQUIRE([AC_CANONICAL_HOST]) 65 | 66 | AC_ARG_ENABLE(portable-binary, [AS_HELP_STRING([--enable-portable-binary], [disable compiler optimizations that would produce unportable binaries])], 67 | acx_maxopt_portable=$enableval, acx_maxopt_portable=no) 68 | 69 | # Try to determine "good" native compiler flags if none specified via CFLAGS 70 | if test "$ac_test_CFLAGS" != "set"; then 71 | case $ax_cv_c_compiler_vendor in 72 | dec) CFLAGS="$CFLAGS -newc -w0 -O5 -ansi_alias -ansi_args -fp_reorder -tune host" 73 | if test "x$acx_maxopt_portable" = xno; then 74 | CFLAGS="$CFLAGS -arch host" 75 | fi;; 76 | 77 | sun) CFLAGS="$CFLAGS -native -fast -xO5 -dalign" 78 | if test "x$acx_maxopt_portable" = xyes; then 79 | CFLAGS="$CFLAGS -xarch=generic" 80 | fi;; 81 | 82 | hp) CFLAGS="$CFLAGS +Oall +Optrs_ansi +DSnative" 83 | if test "x$acx_maxopt_portable" = xyes; then 84 | CFLAGS="$CFLAGS +DAportable" 85 | fi;; 86 | 87 | ibm) if test "x$acx_maxopt_portable" = xno; then 88 | xlc_opt="-qarch=auto -qtune=auto" 89 | else 90 | xlc_opt="-qtune=auto" 91 | fi 92 | AX_CHECK_COMPILE_FLAG($xlc_opt, 93 | CFLAGS="$CFLAGS -O3 -qansialias -w $xlc_opt", 94 | [CFLAGS="$CFLAGS -O3 -qansialias -w" 95 | echo "******************************************************" 96 | echo "* You seem to have the IBM C compiler. It is *" 97 | echo "* recommended for best performance that you use: *" 98 | echo "* *" 99 | echo "* CFLAGS=-O3 -qarch=xxx -qtune=xxx -qansialias -w *" 100 | echo "* ^^^ ^^^ *" 101 | echo "* where xxx is pwr2, pwr3, 604, or whatever kind of *" 102 | echo "* CPU you have. (Set the CFLAGS environment var. *" 103 | echo "* and re-run configure.) For more info, man cc. *" 104 | echo "******************************************************"]) 105 | ;; 106 | 107 | intel) CFLAGS="$CFLAGS -O3 -ansi_alias" 108 | if test "x$acx_maxopt_portable" = xno; then 109 | icc_archflag=unknown 110 | icc_flags="" 111 | case $host_cpu in 112 | i686*|x86_64*) 113 | # icc accepts gcc assembly syntax, so these should work: 114 | AX_GCC_X86_CPUID(0) 115 | AX_GCC_X86_CPUID(1) 116 | case $ax_cv_gcc_x86_cpuid_0 in # see AX_GCC_ARCHFLAG 117 | *:756e6547:6c65746e:49656e69) # Intel 118 | case $ax_cv_gcc_x86_cpuid_1 in 119 | *0?6[[78ab]]?:*:*:*|?6[[78ab]]?:*:*:*|6[[78ab]]?:*:*:*) icc_flags="-xK" ;; 120 | *0?6[[9d]]?:*:*:*|?6[[9d]]?:*:*:*|6[[9d]]?:*:*:*|*1?65?:*:*:*) icc_flags="-xSSE2 -xB -xK" ;; 121 | *0?6e?:*:*:*|?6e?:*:*:*|6e?:*:*:*) icc_flags="-xSSE3 -xP -xO -xB -xK" ;; 122 | *0?6f?:*:*:*|?6f?:*:*:*|6f?:*:*:*|*1?66?:*:*:*) icc_flags="-xSSSE3 -xT -xB -xK" ;; 123 | *1?6[[7d]]?:*:*:*) icc_flags="-xSSE4.1 -xS -xT -xB -xK" ;; 124 | *1?6[[aef]]?:*:*:*|*2?6[[5cef]]?:*:*:*) icc_flags="-xSSE4.2 -xS -xT -xB -xK" ;; 125 | *2?6[[ad]]?:*:*:*) icc_flags="-xAVX -SSE4.2 -xS -xT -xB -xK" ;; 126 | *3?6[[ae]]?:*:*:*) icc_flags="-xCORE-AVX-I -xAVX -SSE4.2 -xS -xT -xB -xK" ;; 127 | *3?6[[cf]]?:*:*:*|*4?6[[56]]?:*:*:*) icc_flags="-xCORE-AVX2 -xCORE-AVX-I -xAVX -SSE4.2 -xS -xT -xB -xK" ;; 128 | *000?f[[346]]?:*:*:*|?f[[346]]?:*:*:*|f[[346]]?:*:*:*) icc_flags="-xSSE3 -xP -xO -xN -xW -xK" ;; 129 | *00??f??:*:*:*|??f??:*:*:*|?f??:*:*:*|f??:*:*:*) icc_flags="-xSSE2 -xN -xW -xK" ;; 130 | esac ;; 131 | esac ;; 132 | esac 133 | if test "x$icc_flags" != x; then 134 | for flag in $icc_flags; do 135 | AX_CHECK_COMPILE_FLAG($flag, [icc_archflag=$flag; break]) 136 | done 137 | fi 138 | AC_MSG_CHECKING([for icc architecture flag]) 139 | AC_MSG_RESULT($icc_archflag) 140 | if test "x$icc_archflag" != xunknown; then 141 | CFLAGS="$CFLAGS $icc_archflag" 142 | fi 143 | fi 144 | ;; 145 | 146 | gnu) 147 | # default optimization flags for gcc on all systems 148 | CFLAGS="$CFLAGS -O3 -fomit-frame-pointer" 149 | 150 | # -malign-double for x86 systems 151 | AX_CHECK_COMPILE_FLAG(-malign-double, CFLAGS="$CFLAGS -malign-double") 152 | 153 | # -fstrict-aliasing for gcc-2.95+ 154 | AX_CHECK_COMPILE_FLAG(-fstrict-aliasing, 155 | CFLAGS="$CFLAGS -fstrict-aliasing") 156 | 157 | # note that we enable "unsafe" fp optimization with other compilers, too 158 | AX_CHECK_COMPILE_FLAG(-ffast-math, CFLAGS="$CFLAGS -ffast-math") 159 | 160 | AX_GCC_ARCHFLAG($acx_maxopt_portable) 161 | ;; 162 | 163 | microsoft) 164 | # default optimization flags for MSVC opt builds 165 | CFLAGS="$CFLAGS -O2" 166 | ;; 167 | esac 168 | 169 | if test -z "$CFLAGS"; then 170 | echo "" 171 | echo "********************************************************" 172 | echo "* WARNING: Don't know the best CFLAGS for this system *" 173 | echo "* Use ./configure CFLAGS=... to specify your own flags *" 174 | echo "* (otherwise, a default of CFLAGS=-O3 will be used) *" 175 | echo "********************************************************" 176 | echo "" 177 | CFLAGS="$CFLAGS -O3" 178 | fi 179 | 180 | AX_CHECK_COMPILE_FLAG($CFLAGS, [], [ 181 | echo "" 182 | echo "********************************************************" 183 | echo "* WARNING: The guessed CFLAGS don't seem to work with *" 184 | echo "* your compiler. *" 185 | echo "* Use ./configure CFLAGS=... to specify your own flags *" 186 | echo "********************************************************" 187 | echo "" 188 | ]) 189 | 190 | fi 191 | ]) 192 | -------------------------------------------------------------------------------- /m4/ax_check_compile_flag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given FLAG works with the current language's compiler 12 | # or gives an error. (Warnings, however, are ignored) 13 | # 14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 15 | # success/failure. 16 | # 17 | # If EXTRA-FLAGS is defined, it is added to the current language's default 18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with 19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to 20 | # force the compiler to issue an error when a bad flag is given. 21 | # 22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE. 23 | # 24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this 25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Guido U. Draheim 30 | # Copyright (c) 2011 Maarten Bosmans 31 | # 32 | # Copying and distribution of this file, with or without modification, are 33 | # permitted in any medium without royalty provided the copyright notice 34 | # and this notice are preserved. This file is offered as-is, without any 35 | # warranty. 36 | 37 | #serial 6 38 | 39 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], 40 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF 41 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl 42 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ 43 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS 44 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" 45 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], 46 | [AS_VAR_SET(CACHEVAR,[yes])], 47 | [AS_VAR_SET(CACHEVAR,[no])]) 48 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) 49 | AS_VAR_IF(CACHEVAR,yes, 50 | [m4_default([$2], :)], 51 | [m4_default([$3], :)]) 52 | AS_VAR_POPDEF([CACHEVAR])dnl 53 | ])dnl AX_CHECK_COMPILE_FLAGS 54 | -------------------------------------------------------------------------------- /m4/ax_compiler_vendor.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_COMPILER_VENDOR 8 | # 9 | # DESCRIPTION 10 | # 11 | # Determine the vendor of the C, C++ or Fortran compiler. The vendor is 12 | # returned in the cache variable $ax_cv_c_compiler_vendor for C, 13 | # $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for 14 | # (modern) Fortran. The value is one of "intel", "ibm", "pathscale", 15 | # "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "portland" (PGI), "gnu" 16 | # (GCC), "sun" (Oracle Developer Studio), "hp", "dec", "borland", 17 | # "comeau", "kai", "lcc", "sgi", "microsoft", "metrowerks", "watcom", 18 | # "tcc" (Tiny CC) or "unknown" (if the compiler cannot be determined). 19 | # 20 | # To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT 21 | # with an appropriate preprocessor-enabled extension. For example: 22 | # 23 | # AC_LANG_PUSH([Fortran]) 24 | # AC_PROG_FC 25 | # AC_FC_PP_SRCEXT([F]) 26 | # AX_COMPILER_VENDOR 27 | # AC_LANG_POP([Fortran]) 28 | # 29 | # LICENSE 30 | # 31 | # Copyright (c) 2008 Steven G. Johnson 32 | # Copyright (c) 2008 Matteo Frigo 33 | # Copyright (c) 2018-19 John Zaitseff 34 | # 35 | # This program is free software: you can redistribute it and/or modify it 36 | # under the terms of the GNU General Public License as published by the 37 | # Free Software Foundation, either version 3 of the License, or (at your 38 | # option) any later version. 39 | # 40 | # This program is distributed in the hope that it will be useful, but 41 | # WITHOUT ANY WARRANTY; without even the implied warranty of 42 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 43 | # Public License for more details. 44 | # 45 | # You should have received a copy of the GNU General Public License along 46 | # with this program. If not, see . 47 | # 48 | # As a special exception, the respective Autoconf Macro's copyright owner 49 | # gives unlimited permission to copy, distribute and modify the configure 50 | # scripts that are the output of Autoconf when processing the Macro. You 51 | # need not follow the terms of the GNU General Public License when using 52 | # or distributing such scripts, even though portions of the text of the 53 | # Macro appear in them. The GNU General Public License (GPL) does govern 54 | # all other use of the material that constitutes the Autoconf Macro. 55 | # 56 | # This special exception to the GPL applies to versions of the Autoconf 57 | # Macro released by the Autoconf Archive. When you make and distribute a 58 | # modified version of the Autoconf Macro, you may extend this special 59 | # exception to the GPL to apply to your modified version as well. 60 | 61 | #serial 30 62 | 63 | AC_DEFUN([AX_COMPILER_VENDOR], [dnl 64 | AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl 65 | dnl If you modify this list of vendors, please add similar support 66 | dnl to ax_compiler_version.m4 if at all possible. 67 | dnl 68 | dnl Note: Do NOT check for GCC first since some other compilers 69 | dnl define __GNUC__ to remain compatible with it. Compilers that 70 | dnl are very slow to start (such as Intel) are listed first. 71 | 72 | vendors=" 73 | intel: __ICC,__ECC,__INTEL_COMPILER 74 | ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__ 75 | pathscale: __PATHCC__,__PATHSCALE__ 76 | clang: __clang__ 77 | cray: _CRAYC 78 | fujitsu: __FUJITSU 79 | sdcc: SDCC,__SDCC 80 | sx: _SX 81 | portland: __PGI 82 | gnu: __GNUC__ 83 | sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95 84 | hp: __HP_cc,__HP_aCC 85 | dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER 86 | borland: __BORLANDC__,__CODEGEARC__,__TURBOC__ 87 | comeau: __COMO__ 88 | kai: __KCC 89 | lcc: __LCC__ 90 | sgi: __sgi,sgi 91 | microsoft: _MSC_VER 92 | metrowerks: __MWERKS__ 93 | watcom: __WATCOMC__ 94 | tcc: __TINYC__ 95 | unknown: UNKNOWN 96 | " 97 | for ventest in $vendors; do 98 | case $ventest in 99 | *:) 100 | vendor=$ventest 101 | continue 102 | ;; 103 | *) 104 | vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")" 105 | ;; 106 | esac 107 | 108 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[ 109 | #if !($vencpp) 110 | thisisanerror; 111 | #endif 112 | ]])], [break]) 113 | done 114 | 115 | ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1` 116 | ]) 117 | ])dnl 118 | -------------------------------------------------------------------------------- /m4/ax_gcc_archflag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_gcc_archflag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_GCC_ARCHFLAG([PORTABLE?], [ACTION-SUCCESS], [ACTION-FAILURE]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro tries to guess the "native" arch corresponding to the target 12 | # architecture for use with gcc's -march=arch or -mtune=arch flags. If 13 | # found, the cache variable $ax_cv_gcc_archflag is set to this flag and 14 | # ACTION-SUCCESS is executed; otherwise $ax_cv_gcc_archflag is set to 15 | # "unknown" and ACTION-FAILURE is executed. The default ACTION-SUCCESS is 16 | # to add $ax_cv_gcc_archflag to the end of $CFLAGS. 17 | # 18 | # PORTABLE? should be either [yes] (default) or [no]. In the former case, 19 | # the flag is set to -mtune (or equivalent) so that the architecture is 20 | # only used for tuning, but the instruction set used is still portable. In 21 | # the latter case, the flag is set to -march (or equivalent) so that 22 | # architecture-specific instructions are enabled. 23 | # 24 | # The user can specify --with-gcc-arch= in order to override the 25 | # macro's choice of architecture, or --without-gcc-arch to disable this. 26 | # 27 | # When cross-compiling, or if $CC is not gcc, then ACTION-FAILURE is 28 | # called unless the user specified --with-gcc-arch manually. 29 | # 30 | # Requires macros: AX_CHECK_COMPILE_FLAG, AX_GCC_X86_CPUID 31 | # 32 | # (The main emphasis here is on recent CPUs, on the principle that doing 33 | # high-performance computing on old hardware is uncommon.) 34 | # 35 | # LICENSE 36 | # 37 | # Copyright (c) 2008 Steven G. Johnson 38 | # Copyright (c) 2008 Matteo Frigo 39 | # Copyright (c) 2014 Tsukasa Oi 40 | # Copyright (c) 2017-2018 Alexey Kopytov 41 | # 42 | # This program is free software: you can redistribute it and/or modify it 43 | # under the terms of the GNU General Public License as published by the 44 | # Free Software Foundation, either version 3 of the License, or (at your 45 | # option) any later version. 46 | # 47 | # This program is distributed in the hope that it will be useful, but 48 | # WITHOUT ANY WARRANTY; without even the implied warranty of 49 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 50 | # Public License for more details. 51 | # 52 | # You should have received a copy of the GNU General Public License along 53 | # with this program. If not, see . 54 | # 55 | # As a special exception, the respective Autoconf Macro's copyright owner 56 | # gives unlimited permission to copy, distribute and modify the configure 57 | # scripts that are the output of Autoconf when processing the Macro. You 58 | # need not follow the terms of the GNU General Public License when using 59 | # or distributing such scripts, even though portions of the text of the 60 | # Macro appear in them. The GNU General Public License (GPL) does govern 61 | # all other use of the material that constitutes the Autoconf Macro. 62 | # 63 | # This special exception to the GPL applies to versions of the Autoconf 64 | # Macro released by the Autoconf Archive. When you make and distribute a 65 | # modified version of the Autoconf Macro, you may extend this special 66 | # exception to the GPL to apply to your modified version as well. 67 | 68 | #serial 22 69 | 70 | AC_DEFUN([AX_GCC_ARCHFLAG], 71 | [AC_REQUIRE([AC_PROG_CC]) 72 | AC_REQUIRE([AC_CANONICAL_HOST]) 73 | AC_REQUIRE([AC_PROG_SED]) 74 | AC_REQUIRE([AX_COMPILER_VENDOR]) 75 | 76 | AC_ARG_WITH(gcc-arch, [AS_HELP_STRING([--with-gcc-arch=], [use architecture for gcc -march/-mtune, instead of guessing])], 77 | ax_gcc_arch=$withval, ax_gcc_arch=yes) 78 | 79 | AC_MSG_CHECKING([for gcc architecture flag]) 80 | AC_MSG_RESULT([]) 81 | AC_CACHE_VAL(ax_cv_gcc_archflag, 82 | [ 83 | ax_cv_gcc_archflag="unknown" 84 | 85 | if test "$GCC" = yes; then 86 | 87 | if test "x$ax_gcc_arch" = xyes; then 88 | ax_gcc_arch="" 89 | if test "$cross_compiling" = no; then 90 | case $host_cpu in 91 | i[[3456]]86*|x86_64*|amd64*) # use cpuid codes 92 | AX_GCC_X86_CPUID(0) 93 | AX_GCC_X86_CPUID(1) 94 | case $ax_cv_gcc_x86_cpuid_0 in 95 | *:756e6547:6c65746e:49656e69) # Intel 96 | case $ax_cv_gcc_x86_cpuid_1 in 97 | *5[[4578]]?:*:*:*) ax_gcc_arch="pentium-mmx pentium" ;; 98 | *5[[123]]?:*:*:*) ax_gcc_arch=pentium ;; 99 | *0?61?:*:*:*|?61?:*:*:*|61?:*:*:*) ax_gcc_arch=pentiumpro ;; 100 | *0?6[[356]]?:*:*:*|?6[[356]]?:*:*:*|6[[356]]?:*:*:*) ax_gcc_arch="pentium2 pentiumpro" ;; 101 | *0?6[[78ab]]?:*:*:*|?6[[78ab]]?:*:*:*|6[[78ab]]?:*:*:*) ax_gcc_arch="pentium3 pentiumpro" ;; 102 | *0?6[[9d]]?:*:*:*|?6[[9d]]?:*:*:*|6[[9d]]?:*:*:*|*1?65?:*:*:*) ax_gcc_arch="pentium-m pentium3 pentiumpro" ;; 103 | *0?6e?:*:*:*|?6e?:*:*:*|6e?:*:*:*) ax_gcc_arch="yonah pentium-m pentium3 pentiumpro" ;; 104 | *0?6f?:*:*:*|?6f?:*:*:*|6f?:*:*:*|*1?66?:*:*:*) ax_gcc_arch="core2 pentium-m pentium3 pentiumpro" ;; 105 | *1?6[[7d]]?:*:*:*) ax_gcc_arch="penryn core2 pentium-m pentium3 pentiumpro" ;; 106 | *1?6[[aef]]?:*:*:*|*2?6e?:*:*:*) ax_gcc_arch="nehalem corei7 core2 pentium-m pentium3 pentiumpro" ;; 107 | *2?6[[5cf]]?:*:*:*) ax_gcc_arch="westmere corei7 core2 pentium-m pentium3 pentiumpro" ;; 108 | *2?6[[ad]]?:*:*:*) ax_gcc_arch="sandybridge corei7-avx corei7 core2 pentium-m pentium3 pentiumpro" ;; 109 | *3?6[[ae]]?:*:*:*) ax_gcc_arch="ivybridge core-avx-i corei7-avx corei7 core2 pentium-m pentium3 pentiumpro" ;; 110 | *3?6[[cf]]?:*:*:*|*4?6[[56]]?:*:*:*) ax_gcc_arch="haswell core-avx2 core-avx-i corei7-avx corei7 core2 pentium-m pentium3 pentiumpro" ;; 111 | *3?6d?:*:*:*|*4?6[[7f]]?:*:*:*|*5?66?:*:*:*) ax_gcc_arch="broadwell core-avx2 core-avx-i corei7-avx corei7 core2 pentium-m pentium3 pentiumpro" ;; 112 | *1?6c?:*:*:*|*2?6[[67]]?:*:*:*|*3?6[[56]]?:*:*:*) ax_gcc_arch="bonnell atom core2 pentium-m pentium3 pentiumpro" ;; 113 | *3?67?:*:*:*|*[[45]]?6[[ad]]?:*:*:*) ax_gcc_arch="silvermont atom core2 pentium-m pentium3 pentiumpro" ;; 114 | *000?f[[012]]?:*:*:*|?f[[012]]?:*:*:*|f[[012]]?:*:*:*) ax_gcc_arch="pentium4 pentiumpro" ;; 115 | *000?f[[346]]?:*:*:*|?f[[346]]?:*:*:*|f[[346]]?:*:*:*) ax_gcc_arch="nocona prescott pentium4 pentiumpro" ;; 116 | # fallback 117 | *5??:*:*:*) ax_gcc_arch=pentium ;; 118 | *??6??:*:*:*) ax_gcc_arch="core2 pentiumpro" ;; 119 | *6??:*:*:*) ax_gcc_arch=pentiumpro ;; 120 | *00??f??:*:*:*|??f??:*:*:*|?f??:*:*:*|f??:*:*:*) ax_gcc_arch="pentium4 pentiumpro" ;; 121 | esac ;; 122 | *:68747541:444d4163:69746e65) # AMD 123 | case $ax_cv_gcc_x86_cpuid_1 in 124 | *5[[67]]?:*:*:*) ax_gcc_arch=k6 ;; 125 | *5[[8]]?:*:*:*) ax_gcc_arch="k6-2 k6" ;; 126 | *5[[9d]]?:*:*:*) ax_gcc_arch="k6-3 k6" ;; 127 | *6[[12]]?:*:*:*) ax_gcc_arch="athlon k7" ;; 128 | *6[[34]]?:*:*:*) ax_gcc_arch="athlon-tbird k7" ;; 129 | *6[[678a]]?:*:*:*) ax_gcc_arch="athlon-xp athlon-4 athlon k7" ;; 130 | *000?f[[4578bcef]]?:*:*:*|?f[[4578bcef]]?:*:*:*|f[[4578bcef]]?:*:*:*|*001?f[[4578bcf]]?:*:*:*|1?f[[4578bcf]]?:*:*:*) ax_gcc_arch="athlon64 k8" ;; 131 | *002?f[[13457bcf]]?:*:*:*|2?f[[13457bcf]]?:*:*:*|*004?f[[138bcf]]?:*:*:*|4?f[[138bcf]]?:*:*:*|*005?f[[df]]?:*:*:*|5?f[[df]]?:*:*:*|*006?f[[8bcf]]?:*:*:*|6?f[[8bcf]]?:*:*:*|*007?f[[cf]]?:*:*:*|7?f[[cf]]?:*:*:*|*00c?f1?:*:*:*|c?f1?:*:*:*|*020?f3?:*:*:*|20?f3?:*:*:*) ax_gcc_arch="athlon64-sse3 k8-sse3 athlon64 k8" ;; 132 | *010?f[[245689a]]?:*:*:*|10?f[[245689a]]?:*:*:*|*030?f1?:*:*:*|30?f1?:*:*:*) ax_gcc_arch="barcelona amdfam10 k8" ;; 133 | *050?f[[12]]?:*:*:*|50?f[[12]]?:*:*:*) ax_gcc_arch="btver1 amdfam10 k8" ;; 134 | *060?f1?:*:*:*|60?f1?:*:*:*) ax_gcc_arch="bdver1 amdfam10 k8" ;; 135 | *060?f2?:*:*:*|60?f2?:*:*:*|*061?f[[03]]?:*:*:*|61?f[[03]]?:*:*:*) ax_gcc_arch="bdver2 bdver1 amdfam10 k8" ;; 136 | *063?f0?:*:*:*|63?f0?:*:*:*) ax_gcc_arch="bdver3 bdver2 bdver1 amdfam10 k8" ;; 137 | *07[[03]]?f0?:*:*:*|7[[03]]?f0?:*:*:*) ax_gcc_arch="btver2 btver1 amdfam10 k8" ;; 138 | # fallback 139 | *0[[13]]??f??:*:*:*|[[13]]??f??:*:*:*) ax_gcc_arch="barcelona amdfam10 k8" ;; 140 | *020?f??:*:*:*|20?f??:*:*:*) ax_gcc_arch="athlon64-sse3 k8-sse3 athlon64 k8" ;; 141 | *05??f??:*:*:*|5??f??:*:*:*) ax_gcc_arch="btver1 amdfam10 k8" ;; 142 | *060?f??:*:*:*|60?f??:*:*:*) ax_gcc_arch="bdver1 amdfam10 k8" ;; 143 | *061?f??:*:*:*|61?f??:*:*:*) ax_gcc_arch="bdver2 bdver1 amdfam10 k8" ;; 144 | *06??f??:*:*:*|6??f??:*:*:*) ax_gcc_arch="bdver3 bdver2 bdver1 amdfam10 k8" ;; 145 | *070?f??:*:*:*|70?f??:*:*:*) ax_gcc_arch="btver2 btver1 amdfam10 k8" ;; 146 | *???f??:*:*:*) ax_gcc_arch="amdfam10 k8" ;; 147 | esac ;; 148 | *:746e6543:736c7561:48727561) # IDT / VIA (Centaur) 149 | case $ax_cv_gcc_x86_cpuid_1 in 150 | *54?:*:*:*) ax_gcc_arch=winchip-c6 ;; 151 | *5[[89]]?:*:*:*) ax_gcc_arch=winchip2 ;; 152 | *66?:*:*:*) ax_gcc_arch=winchip2 ;; 153 | *6[[78]]?:*:*:*) ax_gcc_arch=c3 ;; 154 | *6[[9adf]]?:*:*:*) ax_gcc_arch="c3-2 c3" ;; 155 | esac ;; 156 | esac 157 | if test x"$ax_gcc_arch" = x; then # fallback 158 | case $host_cpu in 159 | i586*) ax_gcc_arch=pentium ;; 160 | i686*) ax_gcc_arch=pentiumpro ;; 161 | esac 162 | fi 163 | ;; 164 | 165 | sparc*) 166 | AC_PATH_PROG([PRTDIAG], [prtdiag], [prtdiag], [$PATH:/usr/platform/`uname -i`/sbin/:/usr/platform/`uname -m`/sbin/]) 167 | cputype=`(((grep cpu /proc/cpuinfo | cut -d: -f2) ; ($PRTDIAG -v |grep -i sparc) ; grep -i cpu /var/run/dmesg.boot ) | head -n 1) 2> /dev/null` 168 | cputype=`echo "$cputype" | tr -d ' -' | $SED 's/SPARCIIi/SPARCII/' |tr $as_cr_LETTERS $as_cr_letters` 169 | case $cputype in 170 | *ultrasparciv*) ax_gcc_arch="ultrasparc4 ultrasparc3 ultrasparc v9" ;; 171 | *ultrasparciii*) ax_gcc_arch="ultrasparc3 ultrasparc v9" ;; 172 | *ultrasparc*) ax_gcc_arch="ultrasparc v9" ;; 173 | *supersparc*|*tms390z5[[05]]*) ax_gcc_arch="supersparc v8" ;; 174 | *hypersparc*|*rt62[[056]]*) ax_gcc_arch="hypersparc v8" ;; 175 | *cypress*) ax_gcc_arch=cypress ;; 176 | esac ;; 177 | 178 | alphaev5) ax_gcc_arch=ev5 ;; 179 | alphaev56) ax_gcc_arch=ev56 ;; 180 | alphapca56) ax_gcc_arch="pca56 ev56" ;; 181 | alphapca57) ax_gcc_arch="pca57 pca56 ev56" ;; 182 | alphaev6) ax_gcc_arch=ev6 ;; 183 | alphaev67) ax_gcc_arch=ev67 ;; 184 | alphaev68) ax_gcc_arch="ev68 ev67" ;; 185 | alphaev69) ax_gcc_arch="ev69 ev68 ev67" ;; 186 | alphaev7) ax_gcc_arch="ev7 ev69 ev68 ev67" ;; 187 | alphaev79) ax_gcc_arch="ev79 ev7 ev69 ev68 ev67" ;; 188 | 189 | powerpc*) 190 | cputype=`((grep cpu /proc/cpuinfo | head -n 1 | cut -d: -f2 | cut -d, -f1 | $SED 's/ //g') ; /usr/bin/machine ; /bin/machine; grep CPU /var/run/dmesg.boot | head -n 1 | cut -d" " -f2) 2> /dev/null` 191 | cputype=`echo $cputype | $SED -e 's/ppc//g;s/ *//g'` 192 | case $cputype in 193 | *750*) ax_gcc_arch="750 G3" ;; 194 | *740[[0-9]]*) ax_gcc_arch="$cputype 7400 G4" ;; 195 | *74[[4-5]][[0-9]]*) ax_gcc_arch="$cputype 7450 G4" ;; 196 | *74[[0-9]][[0-9]]*) ax_gcc_arch="$cputype G4" ;; 197 | *970*) ax_gcc_arch="970 G5 power4";; 198 | *POWER4*|*power4*|*gq*) ax_gcc_arch="power4 970";; 199 | *POWER5*|*power5*|*gr*|*gs*) ax_gcc_arch="power5 power4 970";; 200 | 603ev|8240) ax_gcc_arch="$cputype 603e 603";; 201 | *POWER7*) ax_gcc_arch="power7";; 202 | *POWER8*) ax_gcc_arch="power8";; 203 | *POWER9*) ax_gcc_arch="power9";; 204 | *POWER10*) ax_gcc_arch="power10";; 205 | *) ax_gcc_arch=$cputype ;; 206 | esac 207 | ax_gcc_arch="$ax_gcc_arch powerpc" 208 | ;; 209 | aarch64) 210 | cpuimpl=`grep 'CPU implementer' /proc/cpuinfo 2> /dev/null | cut -d: -f2 | tr -d " " | head -n 1` 211 | cpuarch=`grep 'CPU architecture' /proc/cpuinfo 2> /dev/null | cut -d: -f2 | tr -d " " | head -n 1` 212 | cpuvar=`grep 'CPU variant' /proc/cpuinfo 2> /dev/null | cut -d: -f2 | tr -d " " | head -n 1` 213 | case $cpuimpl in 214 | 0x42) case $cpuarch in 215 | 8) case $cpuvar in 216 | 0x0) ax_gcc_arch="thunderx2t99 vulcan armv8.1-a armv8-a+lse armv8-a native" ;; 217 | esac 218 | ;; 219 | esac 220 | ;; 221 | 0x43) case $cpuarch in 222 | 8) case $cpuvar in 223 | 0x0) ax_gcc_arch="thunderx armv8-a native" ;; 224 | 0x1) ax_gcc_arch="thunderx+lse armv8.1-a armv8-a+lse armv8-a native" ;; 225 | esac 226 | ;; 227 | esac 228 | ;; 229 | esac 230 | ;; 231 | esac 232 | fi # not cross-compiling 233 | fi # guess arch 234 | 235 | if test "x$ax_gcc_arch" != x -a "x$ax_gcc_arch" != xno; then 236 | if test "x[]m4_default([$1],yes)" = xyes; then # if we require portable code 237 | flag_prefixes="-mtune=" 238 | if test "x$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor" = xclang; then flag_prefixes="-march="; fi 239 | # -mcpu=$arch and m$arch generate nonportable code on every arch except 240 | # x86. And some other arches (e.g. Alpha) don't accept -mtune. Grrr. 241 | case $host_cpu in i*86|x86_64*|amd64*) flag_prefixes="$flag_prefixes -mcpu= -m";; esac 242 | else 243 | flag_prefixes="-march= -mcpu= -m" 244 | fi 245 | for flag_prefix in $flag_prefixes; do 246 | for arch in $ax_gcc_arch; do 247 | flag="$flag_prefix$arch" 248 | AX_CHECK_COMPILE_FLAG($flag, [if test "x$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor" = xclang; then 249 | if test "x[]m4_default([$1],yes)" = xyes; then 250 | if test "x$flag" = "x-march=$arch"; then flag=-mtune=$arch; fi 251 | fi 252 | fi; ax_cv_gcc_archflag=$flag; break]) 253 | done 254 | test "x$ax_cv_gcc_archflag" = xunknown || break 255 | done 256 | fi 257 | 258 | fi # $GCC=yes 259 | ]) 260 | AC_MSG_CHECKING([for gcc architecture flag]) 261 | AC_MSG_RESULT($ax_cv_gcc_archflag) 262 | if test "x$ax_cv_gcc_archflag" = xunknown; then 263 | m4_default([$3],:) 264 | else 265 | m4_default([$2], [CFLAGS="$CFLAGS $ax_cv_gcc_archflag"]) 266 | fi 267 | ]) 268 | -------------------------------------------------------------------------------- /m4/ax_gcc_x86_cpuid.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_GCC_X86_CPUID(OP) 8 | # AX_GCC_X86_CPUID_COUNT(OP, COUNT) 9 | # 10 | # DESCRIPTION 11 | # 12 | # On Pentium and later x86 processors, with gcc or a compiler that has a 13 | # compatible syntax for inline assembly instructions, run a small program 14 | # that executes the cpuid instruction with input OP. This can be used to 15 | # detect the CPU type. AX_GCC_X86_CPUID_COUNT takes an additional COUNT 16 | # parameter that gets passed into register ECX before calling cpuid. 17 | # 18 | # On output, the values of the eax, ebx, ecx, and edx registers are stored 19 | # as hexadecimal strings as "eax:ebx:ecx:edx" in the cache variable 20 | # ax_cv_gcc_x86_cpuid_OP. 21 | # 22 | # If the cpuid instruction fails (because you are running a 23 | # cross-compiler, or because you are not using gcc, or because you are on 24 | # a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP 25 | # is set to the string "unknown". 26 | # 27 | # This macro mainly exists to be used in AX_GCC_ARCHFLAG. 28 | # 29 | # LICENSE 30 | # 31 | # Copyright (c) 2008 Steven G. Johnson 32 | # Copyright (c) 2008 Matteo Frigo 33 | # Copyright (c) 2015 Michael Petch 34 | # 35 | # This program is free software: you can redistribute it and/or modify it 36 | # under the terms of the GNU General Public License as published by the 37 | # Free Software Foundation, either version 3 of the License, or (at your 38 | # option) any later version. 39 | # 40 | # This program is distributed in the hope that it will be useful, but 41 | # WITHOUT ANY WARRANTY; without even the implied warranty of 42 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 43 | # Public License for more details. 44 | # 45 | # You should have received a copy of the GNU General Public License along 46 | # with this program. If not, see . 47 | # 48 | # As a special exception, the respective Autoconf Macro's copyright owner 49 | # gives unlimited permission to copy, distribute and modify the configure 50 | # scripts that are the output of Autoconf when processing the Macro. You 51 | # need not follow the terms of the GNU General Public License when using 52 | # or distributing such scripts, even though portions of the text of the 53 | # Macro appear in them. The GNU General Public License (GPL) does govern 54 | # all other use of the material that constitutes the Autoconf Macro. 55 | # 56 | # This special exception to the GPL applies to versions of the Autoconf 57 | # Macro released by the Autoconf Archive. When you make and distribute a 58 | # modified version of the Autoconf Macro, you may extend this special 59 | # exception to the GPL to apply to your modified version as well. 60 | 61 | #serial 10 62 | 63 | AC_DEFUN([AX_GCC_X86_CPUID], 64 | [AX_GCC_X86_CPUID_COUNT($1, 0) 65 | ]) 66 | 67 | AC_DEFUN([AX_GCC_X86_CPUID_COUNT], 68 | [AC_REQUIRE([AC_PROG_CC]) 69 | AC_LANG_PUSH([C]) 70 | AC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1, 71 | [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include ], [ 72 | int op = $1, level = $2, eax, ebx, ecx, edx; 73 | FILE *f; 74 | __asm__ __volatile__ ("xchg %%ebx, %1\n" 75 | "cpuid\n" 76 | "xchg %%ebx, %1\n" 77 | : "=a" (eax), "=r" (ebx), "=c" (ecx), "=d" (edx) 78 | : "a" (op), "2" (level)); 79 | 80 | f = fopen("conftest_cpuid", "w"); if (!f) return 1; 81 | fprintf(f, "%x:%x:%x:%x\n", eax, ebx, ecx, edx); 82 | fclose(f); 83 | return 0; 84 | ])], 85 | [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid], 86 | [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid], 87 | [ax_cv_gcc_x86_cpuid_$1=unknown])]) 88 | AC_LANG_POP([C]) 89 | ]) 90 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /.deps 2 | /.libs 3 | *.lo 4 | /libbeetle.la 5 | /beetle 6 | /beetle.exe 7 | /beetlei 8 | /beetle.1 9 | /tbl_opts.h 10 | /completions 11 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | # Source Makefile.am 2 | # 3 | # (c) Reuben Thomas 2011-2019 4 | # 5 | # The package is distributed under the GNU General Public License version 3, 6 | # or, at your option, any later version. 7 | # 8 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | # RISK. 10 | 11 | AM_CPPFLAGS = -I$(top_builddir)/lib -I$(top_srcdir)/lib $(WARN_CFLAGS) 12 | 13 | lib_LTLIBRARIES = libbeetle.la 14 | libbeetle_la_SOURCES = vm.c debug.c loadobj.c private.h stringify.h 15 | libbeetle_la_LIBADD = $(top_builddir)/lib/libgnu.la 16 | if HAVE_MIJIT 17 | libbeetle_la_LIBADD += $(top_srcdir)/mijit-beetle/target/release/libmijit_beetle.la 18 | endif 19 | 20 | bin_PROGRAMS = beetle 21 | bin_SCRIPTS = beetle-dump 22 | if HAVE_RLWRAP 23 | bin_SCRIPTS += beetlei 24 | endif 25 | man_MANS = beetle.1 beetlei.1 beetle-dump.1 26 | pkgdata_DATA = completions 27 | beetle_LDADD = libbeetle.la $(top_builddir)/lib/libgnu.la 28 | beetle_SOURCES = main.c tbl_opts.h tbl_commands.h tbl_registers.h $(include_HEADERS) 29 | include_HEADERS = beetle.h beetle_aux.h beetle_debug.h beetle_opcodes.h 30 | 31 | completions: tbl_registers.h tbl_commands.h 32 | $(CPP) -I$(srcdir) $(srcdir)/completions.h | grep -v "^#" > $@ 33 | 34 | main.o: tbl_opts.h 35 | 36 | if HAVE_MIJIT 37 | .PHONY: mijit-beetle 38 | $(top_srcdir)/mijit-beetle/target/release/libmijit_beetle.la: mijit-beetle 39 | cargo build --release --manifest-path $(top_srcdir)/mijit-beetle/Cargo.toml 40 | endif 41 | 42 | # Depend on beetle$(EXEEXT) rather than explicitly make-ing it, as otherwise 43 | # we break parallel builds, as libbeetle.la can be built twice in parallel, 44 | # which can fail. 45 | beetle.1: beetle$(EXEEXT) 46 | ## Exit gracefully if beetle.1 is not writeable, such as during distcheck! 47 | $(AM_V_GEN)if ( touch $@.w && rm -f $@.w; ) >/dev/null 2>&1; then \ 48 | $(top_srcdir)/build-aux/missing --run $(HELP2MAN) --no-info \ 49 | --name="Forth virtual machine" \ 50 | --output=$@ ./beetle$(EXEEXT); \ 51 | fi 52 | 53 | do_subst = sed -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \ 54 | -e 's,[@]docdir[@],$(docdir),g' \ 55 | -e 's,[@]RLWRAP[@],$(RLWRAP),g' 56 | 57 | tbl_opts.h: tbl_opts.h.in Makefile 58 | $(do_subst) < $(srcdir)/tbl_opts.h.in > tbl_opts.h 59 | 60 | beetlei: beetlei.in Makefile 61 | $(do_subst) < $(srcdir)/beetlei.in > $@ 62 | chmod +x $@ 63 | 64 | CLOC = cloc --force-lang="C",h 65 | 66 | loc: 67 | $(CLOC) $(libbeetle_la_SOURCES) $(beetle_SOURCES) 68 | 69 | EXTRA_DIST = ARMbeetle.bas beetlei.in beetlei.1 beetle-dump beetle-dump.1 completions.h tbl_opts.h.in vm.c external_syms.h 70 | 71 | DISTCLEANFILES = beetlei beetle.1 completions tbl_opts.h $(include_HEADERS) 72 | -------------------------------------------------------------------------------- /src/beetle-dump: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Dump Beetle object file as hex and disassembly 3 | # (c) Reuben Thomas 2018 4 | # 5 | # The package is distributed under the GNU General Public License version 3, 6 | # or, at your option, any later version. 7 | # 8 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | # RISK. 10 | 11 | if [ $# -ne 1 ]; then 12 | echo Usage: beetle-dump OBJECT-FILE 13 | exit 1 14 | fi 15 | 16 | file=$1 17 | base=${file%.*} 18 | 19 | # Check whether file starts with a hash-bang line 20 | size_file=$file 21 | head -n 1 $file > $base.tmp 22 | if grep '^#!' $base.tmp >/dev/null; then 23 | tail -n +2 $file > $base.tmp 24 | size_file=$base.tmp 25 | fi 26 | size=$((`wc -c < $size_file` - 12)) 27 | rm -f $base.tmp 28 | 29 | echo "load $file\ndump 0+$size\nquit" | beetle > $base.hex 30 | echo "load $file\ndisassemble 0+$size\nquit" | beetle > $base.asm 31 | -------------------------------------------------------------------------------- /src/beetle-dump.1: -------------------------------------------------------------------------------- 1 | .TH BEETLE-DUMP "1" "July 2018" "Beetle" "User Commands" 2 | .SH NAME 3 | beetle-dump \- Dump Beetle object files as hex and disassembly 4 | .SH SYNOPSIS 5 | .B beetle-dump 6 | OBJECT\-FILE 7 | .SH DESCRIPTION 8 | Dump the given file, typically \fIfoo.obj\fR, as hex (\fIfoo.hex\fR) and disassembly (\fIfoo.asm\fR). 9 | .SH "SEE ALSO" 10 | .BR beetle (1) 11 | .SH "REPORTING BUGS" 12 | Report bugs to rrt@sc3d.org. 13 | .PP 14 | \(co Reuben Thomas 1995\-2018 15 | .br 16 | Beetle comes with ABSOLUTELY NO WARRANTY. 17 | .br 18 | You may redistribute copies of Beetle 19 | under the terms of the GNU General Public License. 20 | .br 21 | For more information about these matters, see the file named COPYING. 22 | -------------------------------------------------------------------------------- /src/beetle.h: -------------------------------------------------------------------------------- 1 | // Public data structures and interface calls specified in the VM definition. 2 | // This is the header file to include in programs using an embedded VM. 3 | // 4 | // (c) Reuben Thomas 1994-2018 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #ifndef BEETLE_BEETLE 13 | #define BEETLE_BEETLE 14 | 15 | 16 | #include // for the FILE type 17 | #include 18 | #include 19 | 20 | 21 | // Basic types 22 | typedef uint8_t beetle_BYTE; 23 | typedef int32_t beetle_CELL; 24 | typedef uint32_t beetle_UCELL; 25 | typedef uint64_t beetle_DUCELL; 26 | #define beetle_CHAR_MASK ((1 << CHAR_BIT) - 1) 27 | #define beetle_CELL_BIT (sizeof(beetle_CELL_W) * CHAR_BIT) 28 | #define beetle_CELL_MIN ((beetle_CELL)(1UL << (beetle_CELL_BIT - 1))) 29 | #define beetle_CELL_MAX (UINT32_MAX) 30 | #define beetle_CELL_MASK beetle_CELL_MAX 31 | 32 | // VM registers 33 | 34 | // beetle_ENDISM is fixed at compile-time, which seems reasonable, as 35 | // machines rarely change endianness while switched on! 36 | #ifdef WORDS_BIGENDIAN 37 | #define beetle_ENDISM ((beetle_BYTE)1) 38 | #else 39 | #define beetle_ENDISM ((beetle_BYTE)0) 40 | #endif 41 | 42 | typedef struct { 43 | beetle_UCELL beetle_EP; 44 | beetle_UCELL beetle_I; 45 | beetle_CELL beetle_A; 46 | beetle_UCELL beetle_MEMORY; 47 | beetle_UCELL beetle_SP, beetle_RP; 48 | beetle_UCELL beetle_S0, beetle_R0; 49 | beetle_UCELL beetle_THROW; 50 | beetle_UCELL beetle_BAD; 51 | beetle_UCELL beetle_NOT_ADDRESS; 52 | beetle_UCELL beetle_CHECKED; 53 | } beetle_Registers; 54 | extern beetle_Registers beetle_registers; 55 | #define beetle_R(r) (beetle_registers.beetle_ ## r) 56 | extern beetle_CELL *beetle_M0; 57 | 58 | // Memory access 59 | 60 | // Return value is 0 if OK, or exception code for invalid or unaligned address 61 | int beetle_load_cell(beetle_UCELL addr, beetle_CELL *value); 62 | int beetle_store_cell(beetle_UCELL addr, beetle_CELL value); 63 | int beetle_load_byte(beetle_UCELL addr, beetle_BYTE *value); 64 | int beetle_store_byte(beetle_UCELL addr, beetle_BYTE value); 65 | 66 | int beetle_pre_dma(beetle_UCELL from, beetle_UCELL to); 67 | int beetle_post_dma(beetle_UCELL from, beetle_UCELL to); 68 | 69 | // Interface calls 70 | #define beetle_EXIT_INVALID_OPCODE (-256) 71 | #define beetle_EXIT_UNIMPLEMENTED_LIB (-257) 72 | #define beetle_EXIT_INVALID_SP (-258) 73 | #define beetle_EXIT_INVALID_THROW (-259) 74 | #define beetle_EXIT_SINGLE_STEP (-260) 75 | 76 | uint8_t *native_address_of_range(beetle_UCELL addr, beetle_UCELL length); 77 | beetle_CELL beetle_run(void); 78 | beetle_CELL beetle_single_step(void); 79 | int beetle_load_object(FILE *file, beetle_UCELL address); 80 | 81 | // Additional implementation-specific routines, macros, types and quantities 82 | // init and register_args return 0 on success, otherwise a negative number 83 | int beetle_init(size_t size); 84 | void beetle_destroy(void); 85 | int beetle_register_args(int argc, const char *argv[]); 86 | 87 | #define BEETLE_TRUE beetle_CELL_MASK // VM TRUE flag 88 | #define BEETLE_FALSE ((beetle_CELL)0) // VM FALSE flag 89 | 90 | #define beetle_CELL_W 4 // the width of a cell in bytes 91 | #define beetle_POINTER_W (sizeof(void *) / beetle_CELL_W) // the width of a machine pointer in cells 92 | 93 | // beetle_A union to allow storage of machine pointers in VM beetle_memory 94 | union _CELL_pointer { 95 | beetle_CELL cells[beetle_POINTER_W]; 96 | void (*pointer)(void); 97 | }; 98 | typedef union _CELL_pointer beetle_CELL_pointer; 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/beetle_aux.h: -------------------------------------------------------------------------------- 1 | // Auxiliary public functions. 2 | // These are undocumented and subject to change. 3 | // 4 | // (c) Reuben Thomas 1994-2018 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #ifndef BEETLE_AUX 13 | #define BEETLE_AUX 14 | 15 | 16 | #include // for the FILE type 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | // Stacks size 23 | #define beetle_DEFAULT_STACK_SIZE 16384 24 | 25 | // Memory access 26 | 27 | // Return value is 0 if OK, or exception code for invalid or unaligned address 28 | beetle_CELL beetle_reverse_cell(beetle_CELL value); 29 | int beetle_reverse(beetle_UCELL start, beetle_UCELL length); 30 | 31 | #define beetle_STACK_DIRECTION -1 32 | #define beetle__LOAD_CELL(a, temp) \ 33 | ((exception = exception ? exception : beetle_load_cell((a), &temp)), temp) 34 | #define beetle_LOAD_CELL(a) beetle__LOAD_CELL(a, temp) 35 | #define beetle_STORE_CELL(a, v) \ 36 | (exception = exception ? exception : beetle_store_cell((a), (v))) 37 | #define beetle_LOAD_BYTE(a) \ 38 | ((exception = exception ? exception : beetle_load_byte((a), &byte)), byte) 39 | #define beetle_STORE_BYTE(a, v) \ 40 | (exception = exception ? exception : beetle_store_byte((a), (v))) 41 | #define beetle_PUSH(v) \ 42 | (beetle_R(SP) += beetle_CELL_W * beetle_STACK_DIRECTION, beetle_STORE_CELL(beetle_R(SP), (v))) 43 | #define beetle_POP \ 44 | (beetle_R(SP) -= beetle_CELL_W * beetle_STACK_DIRECTION, beetle_LOAD_CELL(beetle_R(SP) + beetle_CELL_W * beetle_STACK_DIRECTION)) 45 | #define beetle_PUSH_DOUBLE(ud) \ 46 | beetle_PUSH((beetle_UCELL)(ud & beetle_CELL_MASK)); \ 47 | beetle_PUSH((beetle_UCELL)((ud >> beetle_CELL_BIT) & beetle_CELL_MASK)) 48 | #define beetle_POP_DOUBLE \ 49 | (beetle_R(SP) -= 2 * beetle_CELL_W * beetle_STACK_DIRECTION, (beetle_UCELL)beetle_LOAD_CELL(beetle_R(SP) + beetle_CELL_W * beetle_STACK_DIRECTION), temp | \ 50 | ((beetle_DUCELL)(beetle_UCELL)beetle__LOAD_CELL(beetle_R(SP) + 2 * beetle_CELL_W * beetle_STACK_DIRECTION, temp2) << beetle_CELL_BIT)) 51 | #define beetle_PUSH_RETURN(v) \ 52 | (beetle_R(RP) += beetle_CELL_W * beetle_STACK_DIRECTION, beetle_STORE_CELL(beetle_R(RP), (v))) 53 | #define beetle_POP_RETURN \ 54 | (beetle_R(RP) -= beetle_CELL_W * beetle_STACK_DIRECTION, beetle_LOAD_CELL(beetle_R(RP) + beetle_CELL_W * beetle_STACK_DIRECTION)) 55 | #define beetle_STACK_UNDERFLOW(ptr, base) \ 56 | (ptr - base == 0 ? false : (beetle_STACK_DIRECTION > 0 ? ptr < base : ptr > base)) 57 | 58 | uint8_t *native_address_of_range(beetle_UCELL start, beetle_UCELL length); 59 | 60 | // Align a VM address 61 | #define beetle_ALIGN(a) ((a + beetle_CELL_W - 1) & (-beetle_CELL_W)) 62 | 63 | // Check whether a VM address is aligned 64 | #define beetle_IS_ALIGNED(a) (((a) & (beetle_CELL_W - 1)) == 0) 65 | 66 | // Portable left shift (the behaviour of << with overflow (including on any 67 | // negative number) is undefined) 68 | #define beetle_LSHIFT(n, p) \ 69 | (((n) & (beetle_CELL_MAX >> (p))) << (p)) 70 | 71 | // Portable arithmetic right shift (the behaviour of >> on signed 72 | // quantities is implementation-defined in C99) 73 | #define beetle_ARSHIFT(n, p) ((n) = ((n) >> (p)) | ((UCELL)(-((n) < 0)) << (beetle_CELL_BIT - p))) 74 | 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/beetle_debug.h: -------------------------------------------------------------------------------- 1 | // VM debugging functions 2 | // These are undocumented and subject to change. 3 | // 4 | // (c) Reuben Thomas 1994-2018 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #ifndef BEETLE_DEBUG 13 | #define BEETLE_DEBUG 14 | 15 | 16 | int beetle_byte_size(beetle_CELL v); // return number of significant bytes in a beetle_CELL quantity 17 | 18 | void beetle_ass(beetle_BYTE instr); // assemble an instruction 19 | void beetle_lit(beetle_CELL literal); // assemble a cell literal 20 | bool beetle_ilit(beetle_CELL literal); // assemble an immediate literal, returning false if it doesn't fit 21 | void beetle_plit(void (*literal)(void)); // assemble a machine-dependent function pointer literal, 22 | // including the relevant LITERAL instructions 23 | void beetle_start_ass(beetle_UCELL addr); // start assembly at the given address 24 | beetle_UCELL beetle_ass_current(void); // return address of beetle_CELL currently being assembled to 25 | const char *beetle_disass(beetle_BYTE opcode); // disassemble an instruction 26 | beetle_BYTE beetle_toass(const char *token); // convert a instruction to its opcode 27 | 28 | char *beetle_val_data_stack(void); // return the current data stack as a string 29 | void beetle_show_data_stack(void); // show the current contents of the data stack 30 | void beetle_show_return_stack(void); // show the current contents of the return stack 31 | 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/beetle_opcodes.h: -------------------------------------------------------------------------------- 1 | // enum type for the opcodes to make the interpreter more readable. Opcode 2 | // names which are not valid C identifiers have been altered. 3 | // 4 | // (c) Reuben Thomas 1994-2018 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #ifndef BEETLE_OPCODES 13 | #define BEETLE_OPCODES 14 | 15 | 16 | enum { 17 | O_NEXT00, 18 | O_DUP, 19 | O_DROP, 20 | O_SWAP, 21 | O_OVER, 22 | O_ROT, 23 | O_NROT, 24 | O_TUCK, 25 | O_NIP, 26 | O_PICK, 27 | O_ROLL, 28 | O_QDUP, 29 | O_TOR, 30 | O_RFROM, 31 | O_RFETCH, 32 | O_LESS, 33 | O_GREATER, 34 | O_EQUAL, 35 | O_NEQUAL, 36 | O_LESS0, 37 | O_GREATER0, 38 | O_EQUAL0, 39 | O_NEQUAL0, 40 | O_ULESS, 41 | O_UGREATER, 42 | O_ZERO, 43 | O_ONE, 44 | O_MONE, 45 | O_CELL, 46 | O_MCELL, 47 | O_PLUS, 48 | O_MINUS, 49 | O_SWAPMINUS, 50 | O_PLUS1, 51 | O_MINUS1, 52 | O_PLUSCELL, 53 | O_MINUSCELL, 54 | O_STAR, 55 | O_SLASH, 56 | O_MOD, 57 | O_SLASHMOD, 58 | O_USLASHMOD, 59 | O_SSLASHREM, 60 | O_SLASH2, 61 | O_CELLS, 62 | O_ABS, 63 | O_NEGATE, 64 | O_MAX, 65 | O_MIN, 66 | O_INVERT, 67 | O_AND, 68 | O_OR, 69 | O_XOR, 70 | O_LSHIFT, 71 | O_RSHIFT, 72 | O_LSHIFT1, 73 | O_RSHIFT1, 74 | O_FETCH, 75 | O_STORE, 76 | O_CFETCH, 77 | O_CSTORE, 78 | O_PSTORE, 79 | O_SPFETCH, 80 | O_SPSTORE, 81 | O_RPFETCH, 82 | O_RPSTORE, 83 | O_BRANCH, 84 | O_BRANCHI, 85 | O_QBRANCH, 86 | O_QBRANCHI, 87 | O_EXECUTE, 88 | O_FEXECUTE, 89 | O_CALL, 90 | O_CALLI, 91 | O_EXIT, 92 | O_DO, 93 | O_LOOP, 94 | O_LOOPI, 95 | O_PLOOP, 96 | O_PLOOPI, 97 | O_UNLOOP, 98 | O_J, 99 | O_LITERAL, 100 | O_LITERALI, 101 | O_THROW, 102 | O_HALT, 103 | O_EPFETCH, 104 | O_LIB, 105 | O_UNDEFINED, 106 | O_LINK, 107 | O_S0FETCH, 108 | O_S0STORE, 109 | O_R0FETCH, 110 | O_R0STORE, 111 | O_THROWFETCH, 112 | O_THROWSTORE, 113 | O_MEMORYFETCH, 114 | O_BADFETCH, 115 | O_NOT_ADDRESSFETCH, 116 | O_NEXTFF = 0xff 117 | }; 118 | 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /src/beetlei.1: -------------------------------------------------------------------------------- 1 | .TH BEETLEI "1" "July 2018" "Beetle" "User Commands" 2 | .SH NAME 3 | beetlei \- Beetle Forth virtual machine interactive wrapper 4 | .SH SYNOPSIS 5 | .B beetlei 6 | [\fI\,OPTION\/\fR...] [\fI\,OBJECT\-FILE ARGUMENT\/\fR...] 7 | .SH DESCRIPTION 8 | Run Beetle wrapped with \fBrlwrap\fR to provide command-line editing and history. 9 | .SH FILES 10 | .TP 11 | \fI~/.beetle_history\fR 12 | File in which the command-line history is saved. 13 | .SH "SEE ALSO" 14 | .BR beetle (1), 15 | .BR beetle-dump (1), 16 | .BR rlwrap (1) 17 | .SH "REPORTING BUGS" 18 | Report bugs to rrt@sc3d.org. 19 | .PP 20 | \(co Reuben Thomas 1995\-2018 21 | .br 22 | Beetle comes with ABSOLUTELY NO WARRANTY. 23 | .br 24 | You may redistribute copies of Beetle 25 | under the terms of the GNU General Public License. 26 | .br 27 | For more information about these matters, see the file named COPYING. 28 | -------------------------------------------------------------------------------- /src/beetlei.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run Beetle with command-line completion and history 3 | # (c) Reuben Thomas 2018 4 | # 5 | # The package is distributed under the GNU General Public License version 3, 6 | # or, at your option, any later version. 7 | # 8 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | # RISK. 10 | 11 | @RLWRAP@ --no-warnings --break-chars "(){}[],'+=&^%$#@\";|\\" --complete-filenames --case-insensitive --file @pkgdatadir@/completions --history-filename $HOME/.beetle_history beetle "$@" 12 | -------------------------------------------------------------------------------- /src/completions.h: -------------------------------------------------------------------------------- 1 | // Generate list of commands and registers for rlwrap completion 2 | // 3 | // The package is distributed under the GNU General Public License version 3, 4 | // or, at your option, any later version. 5 | // 6 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 7 | // RISK. 8 | 9 | #define C(cmd) cmd 10 | #define REG(reg) reg 11 | #include "tbl_commands.h" 12 | #include "tbl_registers.h" 13 | -------------------------------------------------------------------------------- /src/debug.c: -------------------------------------------------------------------------------- 1 | // Functions useful for VM debugging. 2 | // 3 | // (c) Reuben Thomas 1994-2021 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "config.h" 12 | 13 | #include "external_syms.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "verify.h" 22 | #include "xvasprintf.h" 23 | 24 | #include "beetle.h" 25 | #include "beetle_aux.h" 26 | #include "beetle_debug.h" 27 | #include "beetle_opcodes.h" 28 | 29 | 30 | static int ibytes; // number of opcodes assembled in current instruction word so far 31 | static CELL icell; // accumulator for instructions being assembled 32 | static UCELL current; // where the current instruction word will be stored 33 | static UCELL here; // where we assemble the next instruction word or literal 34 | 35 | 36 | // Return number of bytes required for a CELL-sized quantity 37 | // After https://stackoverflow.com/questions/2589096/find-most-significant-bit-left-most-that-is-set-in-a-bit-array 38 | verify(CELL_BIT == 32); // Code is hard-wired for 32 bits 39 | _GL_ATTRIBUTE_CONST int byte_size(CELL v) 40 | { 41 | static const int pos[32] = { 42 | 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 43 | 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 44 | }; 45 | 46 | if (v < 0) 47 | v = -v; 48 | 49 | v |= v >> 1; // first round down to one less than a power of 2 50 | v |= v >> 2; 51 | v |= v >> 4; 52 | v |= v >> 8; 53 | v |= v >> 16; 54 | 55 | return pos[(UCELL)(v * 0x07C4ACDDU) >> 27] / 8 + 1; 56 | } 57 | 58 | void ass(BYTE instr) 59 | { 60 | icell |= beetle_LSHIFT(instr, ibytes * 8); 61 | store_cell(current, icell); 62 | ibytes++; 63 | if (ibytes == CELL_W) { 64 | current = here; here += CELL_W; 65 | icell = 0; ibytes = 0; 66 | } 67 | } 68 | 69 | void lit(CELL literal) 70 | { 71 | if (ibytes == 0) { store_cell(here - CELL_W, literal); current += CELL_W; } 72 | else { store_cell(here, literal); } 73 | here += CELL_W; 74 | } 75 | 76 | bool ilit(CELL literal) 77 | { 78 | if (byte_size(literal) > CELL_W - ibytes) 79 | return false; 80 | 81 | icell |= beetle_LSHIFT(literal, ibytes * 8); 82 | store_cell(current, icell); current = here; here += CELL_W; 83 | icell = 0; ibytes = 0; 84 | return true; 85 | } 86 | 87 | void plit(void (*literal)(void)) 88 | { 89 | CELL_pointer address; 90 | unsigned i; 91 | address.pointer = literal; 92 | for (i = 0; i < POINTER_W; i++) { 93 | ass(O_LITERAL); 94 | lit(address.cells[i]); 95 | } 96 | } 97 | 98 | void start_ass(UCELL addr) 99 | { 100 | here = addr; ibytes = 0; icell = 0; current = here; here += CELL_W; 101 | } 102 | 103 | _GL_ATTRIBUTE_PURE UCELL ass_current(void) 104 | { 105 | return current; 106 | } 107 | 108 | static const char *mnemonic[UINT8_MAX + 1] = { 109 | "NEXT00", "DUP", "DROP", "SWAP", "OVER", "ROT", "-ROT", "TUCK", 110 | "NIP", "PICK", "ROLL", "?DUP", ">R", "R>", "R@", "<", 111 | ">", "=", "<>", "0<", "0>", "0=", "0<>", "U<", 112 | "U>", "0", "1", "-1", "CELL", "-CELL", "+", "-", 113 | ">-<", "1+", "1-", "CELL+", "CELL-", "*", "/", "MOD", 114 | "/MOD", "U/MOD", "S/REM", "2/", "CELLS", "ABS", "NEGATE", "MAX", 115 | "MIN", "INVERT", "AND", "OR", "XOR", "LSHIFT", "RSHIFT", "1LSHIFT", 116 | "1RSHIFT", "@", "!", "C@", "C!", "+!", "SP@", "SP!", 117 | "RP@", "RP!", "BRANCH", "BRANCHI", "?BRANCH", "?BRANCHI", "EXECUTE", "@EXECUTE", 118 | "CALL", "CALLI", "EXIT", "(DO)", "(LOOP)", "(LOOP)I", "(+LOOP)", "(+LOOP)I", 119 | "UNLOOP", "J", "(LITERAL)", "(LITERAL)I", "THROW", "HALT", "EP@", "LIB", 120 | "UNDEFINED", "LINK", "S0@", "S0!", "R0@", "R0!", "'THROW@", "'THROW!", 121 | "MEMORY@", "'BAD@", "-ADDRESS@", NULL, NULL, NULL, NULL, NULL, 122 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 123 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 124 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 125 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 126 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 127 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 128 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 129 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 130 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 131 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 132 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 133 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 134 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 135 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 136 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 137 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 138 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, "NEXTFF" }; 139 | 140 | _GL_ATTRIBUTE_CONST const char *disass(BYTE opcode) 141 | { 142 | if (mnemonic[opcode] == NULL) return ""; 143 | return mnemonic[opcode]; 144 | } 145 | 146 | _GL_ATTRIBUTE_PURE BYTE toass(const char *token) 147 | { 148 | for (size_t i = 0; i < sizeof(mnemonic) / sizeof(mnemonic[0]); i++) 149 | if (mnemonic[i] && strcmp(token, mnemonic[i]) == 0) return i; 150 | 151 | return O_UNDEFINED; 152 | } 153 | 154 | static char *_val_data_stack(bool with_hex) 155 | { 156 | static char *picture = NULL; 157 | 158 | free(picture); 159 | picture = xasprintf("%s", ""); 160 | if (!STACK_UNDERFLOW(R(SP), R(S0))) 161 | for (UCELL i = R(S0); i != R(SP);) { 162 | CELL c; 163 | char *ptr; 164 | i += CELL_W * STACK_DIRECTION; 165 | int exception = load_cell(i, &c); 166 | if (exception != 0) { 167 | ptr = xasprintf("%sinvalid address!", picture); 168 | free(picture); 169 | picture = ptr; 170 | break; 171 | } 172 | ptr = xasprintf("%s%"PRId32, picture, c); 173 | free(picture); 174 | picture = ptr; 175 | if (with_hex) { 176 | ptr = xasprintf("%s ($%"PRIX32") ", picture, (UCELL)c); 177 | free(picture); 178 | picture = ptr; 179 | } 180 | if (i != R(SP)) { 181 | ptr = xasprintf("%s ", picture); 182 | free(picture); 183 | picture = ptr; 184 | } 185 | } 186 | 187 | return picture; 188 | } 189 | 190 | char *val_data_stack(void) 191 | { 192 | return _val_data_stack(false); 193 | } 194 | 195 | void show_data_stack(void) 196 | { 197 | if (R(SP) == R(S0)) 198 | printf("Data stack empty\n"); 199 | else if (STACK_UNDERFLOW(R(SP), R(S0))) 200 | printf("Data stack underflow\n"); 201 | else 202 | printf("Data stack: %s\n", _val_data_stack(true)); 203 | } 204 | 205 | void show_return_stack(void) 206 | { 207 | if (R(RP) == R(R0)) 208 | printf("Return stack empty\n"); 209 | else if (STACK_UNDERFLOW(R(RP), R(R0))) 210 | printf("Return stack underflow\n"); 211 | else { 212 | printf("Return stack: "); 213 | for (UCELL i = R(R0); i != R(RP);) { 214 | CELL c; 215 | i += CELL_W * STACK_DIRECTION; 216 | int exception = load_cell(i, &c); 217 | if (exception != 0) { 218 | printf("invalid address!\n"); 219 | break; 220 | } 221 | printf("$%"PRIX32" ", (UCELL)c); 222 | } 223 | putchar('\n'); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/external_syms.h: -------------------------------------------------------------------------------- 1 | #define ALIGN beetle_ALIGN 2 | #define ARSHIFT beetle_ARSHIFT 3 | #define ass beetle_ass 4 | #define ass_current beetle_ass_current 5 | #define BYTE beetle_BYTE 6 | #define byte_size beetle_byte_size 7 | #define CELL beetle_CELL 8 | #define CELL_BIT beetle_CELL_BIT 9 | #define CELL_MAX beetle_CELL_MAX 10 | #define CELL_MIN beetle_CELL_MIN 11 | #define CELL_pointer beetle_CELL_pointer 12 | #define CELL_W beetle_CELL_W 13 | #define CHAR_MASK beetle_CHAR_MASK 14 | #define destroy beetle_destroy 15 | #define disass beetle_disass 16 | #define DUCELL beetle_DUCELL 17 | #define ENDISM beetle_ENDISM 18 | #define EXIT_INVALID_OPCODE beetle_EXIT_INVALID_OPCODE 19 | #define EXIT_INVALID_SP beetle_EXIT_INVALID_SP 20 | #define EXIT_INVALID_THROW beetle_EXIT_INVALID_THROW 21 | #define EXIT_SINGLE_STEP beetle_EXIT_SINGLE_STEP 22 | #define EXIT_UNIMPLEMENTED_LIB beetle_EXIT_UNIMPLEMENTED_LIB 23 | #define HASHR beetle_HASHR 24 | #define HASHS beetle_HASHS 25 | #define ilit beetle_ilit 26 | #define init beetle_init 27 | #define IS_ALIGNED beetle_IS_ALIGNED 28 | #define lit beetle_lit 29 | #define load_byte beetle_load_byte 30 | #define LOAD_BYTE beetle_LOAD_BYTE 31 | #define load_cell beetle_load_cell 32 | #define LOAD_CELL beetle_LOAD_CELL 33 | #define load_object beetle_load_object 34 | #define M0 beetle_M0 35 | #define memory beetle_memory 36 | #define mem_realloc beetle_mem_realloc 37 | #define plit beetle_plit 38 | #define POINTER_W beetle_POINTER_W 39 | #define POP beetle_POP 40 | #define POP_DOUBLE beetle_POP_DOUBLE 41 | #define POP_RETURN beetle_POP_RETURN 42 | #define post_dma beetle_post_dma 43 | #define pre_dma beetle_pre_dma 44 | #define PUSH beetle_PUSH 45 | #define PUSH_DOUBLE beetle_PUSH_DOUBLE 46 | #define PUSH_RETURN beetle_PUSH_RETURN 47 | #define R(r) beetle_R(r) 48 | #define register_args beetle_register_args 49 | #define reverse beetle_reverse 50 | #define reverse_cell beetle_reverse_cell 51 | #define run beetle_run 52 | #define show_data_stack beetle_show_data_stack 53 | #define show_return_stack beetle_show_return_stack 54 | #define single_step beetle_single_step 55 | #define STACK_DIRECTION beetle_STACK_DIRECTION 56 | #define STACK_UNDERFLOW beetle_STACK_UNDERFLOW 57 | #define start_ass beetle_start_ass 58 | #define store_byte beetle_store_byte 59 | #define STORE_BYTE beetle_STORE_BYTE 60 | #define store_cell beetle_store_cell 61 | #define STORE_CELL beetle_STORE_CELL 62 | #define toass beetle_toass 63 | #define UCELL beetle_UCELL 64 | #define val_data_stack beetle_val_data_stack 65 | -------------------------------------------------------------------------------- /src/loadobj.c: -------------------------------------------------------------------------------- 1 | // The interface call load_object(file, address) : integer. 2 | // 3 | // (c) Reuben Thomas 1995-2018 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "config.h" 12 | 13 | #include "external_syms.h" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "beetle.h" 20 | #include "beetle_aux.h" 21 | 22 | 23 | int load_object(FILE *file, UCELL address) 24 | { 25 | if (!IS_ALIGNED(address)) 26 | return -1; 27 | 28 | size_t len = strlen("BEETLE"); 29 | char magic[7]; 30 | assert(len + 1 <= sizeof(magic)); 31 | 32 | // Skip any #! header 33 | if (fread(&magic[0], 1, 2, file) != 2) 34 | return -3; 35 | size_t read = 2; 36 | if (magic[0] == '#' && magic[1] == '!') { 37 | while (getc(file) != '\n') 38 | ; 39 | read = 0; 40 | } 41 | 42 | if (fread(&magic[read], 1, sizeof(magic) - read, file) != sizeof(magic) - read) 43 | return -3; 44 | if (strncmp(magic, "BEETLE", sizeof(magic))) 45 | return -2; 46 | 47 | uint8_t endism; 48 | if (fread(&endism, 1, 1, file) != 1) 49 | return -3; 50 | if (endism != 0 && endism != 1) 51 | return -2; 52 | int reversed = endism ^ ENDISM; 53 | 54 | UCELL length = 0; 55 | if (fread(&length, 1, CELL_W, file) != CELL_W) 56 | return -3; 57 | if (reversed) 58 | length = (UCELL)reverse_cell((CELL)length); 59 | 60 | uint8_t *ptr = native_address_of_range(address, length * CELL_W); 61 | if (ptr == NULL) 62 | return -1; 63 | 64 | if (fread(ptr, CELL_W, length, file) != length) 65 | return -3; 66 | if (reversed) 67 | reverse(address, length); 68 | 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /src/private.h: -------------------------------------------------------------------------------- 1 | // Private implementation-specific APIs that are shared between modules. 2 | // 3 | // (c) Reuben Thomas 1994-2021 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #ifndef BEETLE_PRIVATE 12 | #define BEETLE_PRIVATE 13 | 14 | 15 | // Memory access 16 | 17 | // Address checking 18 | #define CHECK_ADDRESS(a, cond, code, label) \ 19 | if (!(cond)) { \ 20 | R(NOT_ADDRESS) = a; \ 21 | exception = code; \ 22 | goto label; \ 23 | } 24 | 25 | #define CHECK_ALIGNED(a) \ 26 | CHECK_ADDRESS(a, IS_ALIGNED(a), -23, badadr) 27 | 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/stringify.h: -------------------------------------------------------------------------------- 1 | // Macros to stringify macros. 2 | // 3 | // (c) Reuben Thomas 2016-2018 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #define xstr(s) #s 12 | #define str(s) xstr(s) 13 | -------------------------------------------------------------------------------- /src/tbl_commands.h: -------------------------------------------------------------------------------- 1 | // List of debugger commands. 2 | // 3 | // (c) Reuben Thomas 2018-2019 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | C(BLITERAL) 12 | C(COUNTS) 13 | C(DFROM) 14 | C(DISASSEMBLE) 15 | C(DUMP) 16 | C(FROM) 17 | C(INFO) 18 | C(ILITERAL) 19 | C(LITERAL) 20 | C(LOAD) 21 | C(PLITERAL) 22 | C(QUIT) 23 | C(RFROM) 24 | C(RUN) 25 | C(SAVE) 26 | C(STEP) 27 | C(TOD) 28 | C(TOR) 29 | C(TRACE) 30 | -------------------------------------------------------------------------------- /src/tbl_opts.h.in: -------------------------------------------------------------------------------- 1 | // Command-line help. 2 | // 3 | // Copyright (c) 2009-2018 Reuben Thomas 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | // D: documentation line 12 | // O: Option 13 | // A: Action 14 | // 15 | // D(text) 16 | // O(long name, short name ('\0' for none), argument, argument docstring, docstring) 17 | // A(argument, docstring) 18 | 19 | #include "stringify.h" 20 | 21 | #define MEMORY_MESSAGE(type, max, def) \ 22 | "set " type " size to the given NUMBER of cells\n" \ 23 | " 0 < NUMBER <= " str(max) " [default " str(def) "]" 24 | OPT("memory", 'm', required_argument, "NUMBER", MEMORY_MESSAGE("memory", MAX_MEMORY, DEFAULT_MEMORY)) 25 | OPT("debug", 'd', no_argument, "", "enter debugger on exception") 26 | OPT("commands", 'c', required_argument, "FILE", "execute commands from FILE") 27 | OPT("help", '\0', no_argument, "", "display this help message and exit") 28 | OPT("version", '\0', no_argument, "", "display version information and exit") 29 | ARG("OBJECT-FILE", "load and run object OBJECT-FILE") 30 | DOC("") 31 | DOC("The ARGUMENTs are available to Beetle.") 32 | DOC("") 33 | DOC("If no non-option arguments are given, Beetle starts in the debugger,") 34 | DOC("which is documented in @docdir@/shell.pdf.") 35 | DOC("If debugger commands are supplied with --commands, and standard input") 36 | DOC("is a terminal, the debugger is entered after the commands have been") 37 | DOC("executed.") 38 | DOC("") 39 | DOC("Report bugs to " PACKAGE_BUGREPORT ".") 40 | -------------------------------------------------------------------------------- /src/tbl_registers.h: -------------------------------------------------------------------------------- 1 | // List of VM register names. 2 | // 3 | // (c) Reuben Thomas 2018 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | REG(A) 12 | REG(NOT_ADDRESS) 13 | REG(BAD) 14 | REG(CHECKED) 15 | REG(ENDISM) 16 | REG(EP) 17 | REG(I) 18 | REG(M0) 19 | REG(MEMORY) 20 | REG(RP) 21 | REG(R0) 22 | REG(SP) 23 | REG(S0) 24 | REG(THROW) 25 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.log 3 | *.trs 4 | /.deps 5 | /.libs 6 | /hello.obj 7 | /hello.output 8 | /arithmetic 9 | /branch 10 | /comparison 11 | /doloop 12 | /exceptions 13 | /init 14 | /lib 15 | /link 16 | /literals 17 | /load_object 18 | /logic 19 | /memory 20 | /registers 21 | /run 22 | /single_step 23 | /stack 24 | /_Noreturn.h 25 | /arg-nonnull.h 26 | /c++defs.h 27 | /dummy.c 28 | /warn-on-use.h 29 | -------------------------------------------------------------------------------- /tests/Makefile.am: -------------------------------------------------------------------------------- 1 | # Tests Makefile.am 2 | # 3 | # (c) Reuben Thomas 2011-2020 4 | # 5 | # The package is distributed under the GNU General Public License version 3, 6 | # or, at your option, any later version. 7 | # 8 | # THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | # RISK. 10 | 11 | AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_builddir)/lib -I$(top_srcdir)/lib $(WARN_CFLAGS) 12 | 13 | LOG_COMPILER = $(srcdir)/run-test 14 | 15 | LDADD = $(top_builddir)/src/libbeetle.la 16 | 17 | check_PROGRAMS = $(TESTS) load_object 18 | 19 | TESTS = arithmetic branch comparison doloop init lib literals logic memory \ 20 | registers stack single_step run exceptions link 21 | TESTS_ENVIRONMENT = \ 22 | export LIBTOOL=$(top_builddir)/libtool; 23 | 24 | test-parser: 25 | ( $(TESTS_ENVIRONMENT) $(LOG_COMPILER) $(top_builddir)/src/beetle$(EXEEXT) --commands $(srcdir)/hello.txt ) && \ 26 | ( $(TESTS_ENVIRONMENT) $(LOG_COMPILER) $(top_builddir)/src/beetle$(EXEEXT) ./hello.obj > hello.output ) && \ 27 | diff hello.output $(srcdir)/hello.correct 28 | 29 | do_load_object: load_object 30 | $(TESTS_ENVIRONMENT) $(LOG_COMPILER) $(builddir)/load_object $(srcdir) 31 | 32 | check-local: do_load_object test-parser 33 | 34 | EXTRA_DIST = run-test tests.h badobj1 badobj2 badobj3 badobj4 testobj1 testobj2 testobj3 hello.txt hello.correct 35 | 36 | DISTCLEANFILES = hello.obj hello.output 37 | -------------------------------------------------------------------------------- /tests/arithmetic.c: -------------------------------------------------------------------------------- 1 | // Test the arithmetic operators. Also uses the NEXT, SWAP, ROT, 2 | // DROP, and (LITERAL)I instructions. Since unsigned arithmetic 3 | // overflow behaviour is guaranteed by the ISO C standard, we only test 4 | // the stack handling and basic correctness of the operators here, 5 | // assuming that if the arithmetic works in one case, it will work in 6 | // all. Note that the correct stack values are not quite independent 7 | // of the cell size (in CELL_W and str(CELL_W)); some stack pictures 8 | // implicitly refer to it. 9 | // 10 | // (c) Reuben Thomas 1994-2020 11 | // 12 | // The package is distributed under the GNU General Public License version 3, 13 | // or, at your option, any later version. 14 | // 15 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 16 | // RISK. 17 | 18 | #include "tests.h" 19 | 20 | 21 | const char *correct[] = { 22 | "", "0", "0 1", "0 1 -1", "0 1 -1 " str(CELL_W), 23 | "0 1 -1 " str(CELL_W) " -" str(CELL_W), "0 1 " str(CELL_W) " -" str(CELL_W) " -1", 24 | "0 1 " str(CELL_W) " -5", "0 1 -1", "0 2", "0 3", "0 2", "2 0", "2 " str(CELL_W), 25 | "2 0", "2 0 -1", "2 0 -1 " str(CELL_W), "2 0 -" str(CELL_W), "2 -" str(CELL_W), "-2 -1", 26 | "2", "2 -1", "0", "1", str(CELL_W), "2", "", str(CELL_W), "-" str(CELL_W), str(CELL_W), 27 | str(CELL_W), str(CELL_W) " 1", str(CELL_W), str(CELL_W) " -" str(CELL_W), "-" str(CELL_W), 28 | "-" str(CELL_W) " 3", "-1 -1", "-1", "-1 -2", "1 1" }; 29 | 30 | 31 | int main(void) 32 | { 33 | init(256); 34 | 35 | start_ass(R(EP)); 36 | ass(O_ZERO); ass(O_ONE); ass(O_MONE); ass(O_CELL); 37 | ass(O_MCELL); ass(O_ROT); ass(O_PLUS); ass(O_PLUS); 38 | ass(O_MINUS); ass(O_PLUS1); ass(O_MINUS1); ass(O_SWAP); 39 | ass(O_PLUSCELL); ass(O_MINUSCELL); ass(O_MONE); ass(O_CELL); 40 | ass(O_STAR); ass(O_SWAPMINUS); ass(O_SLASHMOD); ass(O_SLASH); 41 | ass(O_MONE); ass(O_MOD); ass(O_PLUS1); ass(O_CELLS); 42 | ass(O_SLASH2); ass(O_DROP); ass(O_CELL); ass(O_NEGATE); 43 | ass(O_ABS); ass(O_ABS); ass(O_ONE); ass(O_MAX); 44 | ass(O_MCELL); ass(O_MIN); ass(O_LITERALI); ilit(3); 45 | ass(O_SSLASHREM); ass(O_DROP); ass(O_LITERALI); ilit(-2); 46 | ass(O_USLASHMOD); 47 | 48 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 49 | 50 | for (size_t i = 0; i < sizeof(correct) / sizeof(correct[0]); i++) { 51 | show_data_stack(); 52 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 53 | if (strcmp(correct[i - i / 5], val_data_stack())) { 54 | printf("Error in arithmetic tests: EP = %"PRIu32"\n", R(EP)); 55 | exit(1); 56 | } 57 | assert(single_step() == EXIT_SINGLE_STEP); 58 | printf("I = %s\n", disass(R(I))); 59 | } 60 | 61 | printf("Arithmetic tests ran OK\n"); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /tests/badobj1: -------------------------------------------------------------------------------- 1 | BEETLE 2 | -------------------------------------------------------------------------------- /tests/badobj2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/tests/badobj2 -------------------------------------------------------------------------------- /tests/badobj3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrthomas/beetle/fe8b95bed7617ef448deaf67d3b5fc3eac85c217/tests/badobj3 -------------------------------------------------------------------------------- /tests/badobj4: -------------------------------------------------------------------------------- 1 | BEE -------------------------------------------------------------------------------- /tests/branch.c: -------------------------------------------------------------------------------- 1 | // Test the branch instructions. Also uses other instructions with lower 2 | // opcodes than the instructions tested (i.e. those already tested). 3 | // See exceptions.c for address exception handling tests. 4 | // The test program contains an infinite loop, but this is only executed 5 | // once. 6 | // 7 | // (c) Reuben Thomas 1994-2020 8 | // 9 | // The package is distributed under the GNU General Public License version 3, 10 | // or, at your option, any later version. 11 | // 12 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 13 | // RISK. 14 | 15 | #include "tests.h" 16 | 17 | 18 | unsigned correct[] = { 4, 100, 52, 10004, 10004, 10008, 10008, 10012, 10012, 11004, 19 | 11004, 11020, 11024, 68, 204, 304, 212, 72, 76, 80, 80, 80, 68 }; 20 | 21 | 22 | int main(void) 23 | { 24 | init(4096); 25 | 26 | start_ass(R(EP)); 27 | ass(O_BRANCHI); ilit(23); 28 | 29 | start_ass(96); 30 | ass(O_BRANCHI); ilit(-13); 31 | 32 | start_ass(48); 33 | ass(O_BRANCH); lit(10000); 34 | 35 | start_ass(10000); 36 | ass(O_ONE); ass(O_QBRANCHI); ilit(0); 37 | ass(O_ONE); ass(O_QBRANCH); lit(0); ass(O_ZERO); ass(O_QBRANCH); lit(11000); 38 | 39 | start_ass(11000); 40 | ass(O_ZERO); ass(O_QBRANCHI); ilit(3); 41 | 42 | start_ass(11016); 43 | ass(O_LITERALI); ilit(64); 44 | ass(O_EXECUTE); 45 | 46 | start_ass(64); 47 | ass(O_CALLI); ilit(33); 48 | ass(O_LITERALI); ilit(64); 49 | ass(O_LITERALI); ilit(20); 50 | ass(O_TUCK); ass(O_STORE); ass(O_FEXECUTE); 51 | 52 | start_ass(200); 53 | ass(O_CALL); lit(300); ilit(0); 54 | ass(O_EXIT); 55 | 56 | start_ass(300); 57 | ass(O_EXIT); 58 | 59 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 60 | 61 | for (size_t i = 0; i < sizeof(correct) / sizeof(correct[0]); i++) { 62 | printf("Instruction %zu: EP = %u; should be %u\n\n", i, R(EP), correct[i]); 63 | if (correct[i] != R(EP)) { 64 | printf("Error in branch tests: EP = %"PRIu32"\n", R(EP)); 65 | exit(1); 66 | } 67 | assert(single_step() == EXIT_SINGLE_STEP); 68 | printf("I = %s\n", disass(R(I))); 69 | } 70 | 71 | printf("Branch tests ran OK\n"); 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /tests/comparison.c: -------------------------------------------------------------------------------- 1 | // Test the comparison operators. Also uses the NEXT instruction. We 2 | // only test simple cases here, assuming that the C compiler's comparison 3 | // routines will work for other cases. 4 | // 5 | // (c) Reuben Thomas 1994-2020 6 | // 7 | // The package is distributed under the GNU General Public License version 3, 8 | // or, at your option, any later version. 9 | // 10 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 11 | // RISK. 12 | 13 | #include "tests.h" 14 | 15 | 16 | int exception = 0; 17 | CELL temp; 18 | 19 | CELL correct[] = { 0, -1, 0, -1, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, -1, 0, 0, 20 | -1, 0, 0, -1, 0, 0, -1, 0, 0, -1}; 21 | 22 | 23 | static void stack1(void) 24 | { 25 | R(SP) = R(S0); // empty the stack 26 | 27 | PUSH(-4); PUSH(3); 28 | PUSH(2); PUSH(2); 29 | PUSH(1); PUSH(3); 30 | PUSH(3); PUSH(1); 31 | } 32 | 33 | static void stack2(void) 34 | { 35 | R(SP) = R(S0); // empty the stack 36 | 37 | PUSH(1); PUSH(-1); 38 | PUSH(237); PUSH(237); 39 | } 40 | 41 | static void stack3(void) 42 | { 43 | R(SP) = R(S0); // empty the stack 44 | 45 | PUSH(-1); PUSH(0); PUSH(237); 46 | } 47 | 48 | static void step(unsigned start, unsigned end) 49 | { 50 | if (end > start) 51 | for (unsigned i = start; i < end; i++) { 52 | single_step(); 53 | printf("I = %s\n", disass(R(I))); 54 | if (R(I) != O_NEXT00) { 55 | printf("Result: %d; correct result: %d\n\n", LOAD_CELL(R(SP)), 56 | correct[i - i / 5]); 57 | if (correct[i - i / 5] != LOAD_CELL(R(SP))) { 58 | printf("Error in comparison tests: EP = %"PRIu32"\n", R(EP)); 59 | exit(1); 60 | } 61 | (void)POP; // drop result of comparison 62 | } 63 | else 64 | putchar('\n'); 65 | } 66 | } 67 | 68 | int main(void) 69 | { 70 | init(256); 71 | 72 | start_ass(R(EP)); 73 | ass(O_LESS); ass(O_LESS); ass(O_LESS); ass(O_LESS); 74 | ass(O_GREATER); ass(O_GREATER); ass(O_GREATER); ass(O_GREATER); 75 | ass(O_EQUAL); ass(O_EQUAL); ass(O_NEQUAL); ass(O_NEQUAL); 76 | ass(O_LESS0); ass(O_LESS0); ass(O_LESS0); ass(O_GREATER0); 77 | ass(O_GREATER0); ass(O_GREATER0); ass(O_EQUAL0); ass(O_EQUAL0); 78 | ass(O_ULESS); ass(O_ULESS); ass(O_ULESS); ass(O_ULESS); 79 | ass(O_UGREATER); ass(O_UGREATER); ass(O_UGREATER); ass(O_UGREATER); 80 | 81 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 82 | 83 | stack1(); // set up the stack with four standard pairs to compare 84 | step(0, 5); // do the < tests 85 | stack1(); 86 | step(5, 10); // do the > tests 87 | stack2(); // set up the stack with two standard pairs to compare 88 | step(10, 12); // do the = tests 89 | stack2(); 90 | step(12, 15); // do the <> tests 91 | stack3(); // set up the stack with three standard values 92 | step(15, 18); // do the 0< tests 93 | stack3(); 94 | step(18, 22); // do the 0> tests 95 | R(SP) = R(S0); PUSH(237); PUSH(0); // set up the stack with two values 96 | step(22, 25); // do the 0= tests 97 | stack1(); // set up the stack with four standard pairs to compare 98 | step(25, 30); // do the U< tests 99 | stack1(); 100 | step(30, 35); // do the U> tests 101 | 102 | assert(exception == 0); 103 | printf("Comparison tests ran OK\n"); 104 | return 0; 105 | } 106 | -------------------------------------------------------------------------------- /tests/doloop.c: -------------------------------------------------------------------------------- 1 | // Test the DO...LOOP support instructions. Also uses instructions with 2 | // lower opcodes. 3 | // 4 | // (c) Reuben Thomas 1994-2020 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #include "tests.h" 13 | 14 | 15 | const char *correct[] = { "0 1 2", "3 2 1 0", "1", "1 2 3 4", "1 1" }; 16 | 17 | 18 | int main(void) 19 | { 20 | int exception = 0; 21 | CELL temp = 0; 22 | 23 | init(256); 24 | 25 | start_ass(R(EP)); 26 | // Address 0: 3 0 DO R> LOOP 27 | ass(O_LITERALI); ilit(3); 28 | ass(O_ZERO); ass(O_DO); ass(O_NEXT00); ass(O_NEXT00); 29 | ass(O_RFETCH); ass(O_LOOP); lit(8); ass(O_NEXT00); ass(O_NEXT00); 30 | // Address 16: 0 3 DO R> -1 +LOOP 31 | ass(O_ZERO); ass(O_LITERAL); lit(3); ass(O_DO); ass(O_NEXT00); 32 | ass(O_RFETCH); ass(O_MONE); ass(O_PLOOPI); ilit(-1); 33 | // Address 32: CELL 1 DO R> CELL +LOOP 34 | ass(O_CELL); ass(O_ONE); ass(O_DO); ass(O_NEXT00); 35 | ass(O_RFETCH); ass(O_CELL); ass(O_PLOOP); lit(32); ass(O_NEXT00); 36 | // Address 40: 1 1 DO R> LOOP (infinite loop!) 37 | ass(O_ONE); ass(O_ONE); ass(O_DO); ass(O_NEXT00); 38 | ass(O_RFETCH); ass(O_LOOPI); ilit(-1); 39 | // Address 48: 1 >R CELL >R -1 >R J DUP UNLOOP 40 | ass(O_ONE); ass(O_TOR); ass(O_CELL); ass(O_TOR); 41 | ass(O_MONE); ass(O_TOR); ass(O_J); ass(O_NEXT00); 42 | ass(O_DUP); ass(O_UNLOOP); 43 | 44 | assert(single_step() == EXIT_SINGLE_STEP); 45 | 46 | while (R(EP) < 20) assert(single_step() == EXIT_SINGLE_STEP); 47 | show_data_stack(); 48 | printf("Correct stack: %s\n\n", correct[0]); 49 | if (strcmp(correct[0], val_data_stack())) { 50 | printf("Error in DO...LOOP tests: EP = %"PRIu32"\n", R(EP)); 51 | exit(1); 52 | } 53 | R(SP) = R(S0); 54 | 55 | while (R(EP) < 32) assert(single_step() == EXIT_SINGLE_STEP); 56 | show_data_stack(); 57 | printf("Correct stack: %s\n\n", correct[1]); 58 | if (strcmp(correct[1], val_data_stack())) { 59 | printf("Error in DO...LOOP tests: EP = %"PRIu32"\n", R(EP)); 60 | exit(1); 61 | } 62 | R(SP) = R(S0); 63 | 64 | while (R(EP) < 40) assert(single_step() == EXIT_SINGLE_STEP); 65 | show_data_stack(); 66 | printf("Correct stack: %s\n\n", correct[2]); 67 | if (strcmp(correct[2], val_data_stack())) { 68 | printf("Error in DO...LOOP tests: EP = %"PRIu32"\n", R(EP)); 69 | exit(1); 70 | } 71 | R(SP) = R(S0); 72 | 73 | for (int i = 0; i < 12; i++) assert(single_step() == EXIT_SINGLE_STEP); 74 | show_data_stack(); 75 | printf("Correct stack: %s\n\n", correct[3]); 76 | if (strcmp(correct[3], val_data_stack())) { 77 | printf("Error in DO...LOOP tests: EP = %"PRIu32"\n", R(EP)); 78 | exit(1); 79 | } 80 | R(SP) = R(S0); 81 | 82 | assert(R(EP) == 48); 83 | R(A) = 0; R(I) = 0; // Exit infinite loop 84 | while (R(EP) < 60) assert(single_step() == EXIT_SINGLE_STEP); 85 | CELL ret3 = LOAD_CELL(R(RP) - 2 * CELL_W * STACK_DIRECTION); 86 | printf("3rd item on return stack is %"PRId32" (should be %"PRId32")\n", ret3, LOAD_CELL(R(SP))); 87 | if (ret3 != LOAD_CELL(R(SP))) { 88 | printf("Error in DO...LOOP tests: EP = %"PRIu32"\n", R(EP)); 89 | exit(1); 90 | } 91 | 92 | assert(single_step() == EXIT_SINGLE_STEP); 93 | assert(single_step() == EXIT_SINGLE_STEP); 94 | show_data_stack(); 95 | printf("Correct stack: %s\n\n", correct[4]); 96 | if (strcmp(correct[4], val_data_stack())) { 97 | printf("Error in DO...LOOP tests: EP = %"PRIu32"\n", R(EP)); 98 | exit(1); 99 | } 100 | 101 | assert(exception == 0); 102 | printf("DO...LOOP tests ran OK\n"); 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /tests/exceptions.c: -------------------------------------------------------------------------------- 1 | // Test the VM-generated exceptions and HALT codes. 2 | // 3 | // (c) Reuben Thomas 1995-2020 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "tests.h" 12 | 13 | 14 | CELL result[] = { EXIT_INVALID_SP, EXIT_INVALID_SP, 42, 0, -23, -23, -10, -9, -9, -23, EXIT_INVALID_OPCODE, EXIT_INVALID_THROW }; 15 | UCELL bad[] = { -1, -1, -1, 28, 40, 44, 48, 16388, 64, 68, 72, 80 }; 16 | UCELL address[] = { -16, 16384, 0, 0, 5, 1, 0, 16384, -20, 1, 0, 1 }; 17 | UCELL test[sizeof(result) / sizeof(result[0])]; 18 | 19 | 20 | int main(void) 21 | { 22 | init(4096); 23 | 24 | start_ass(0); 25 | // test 1: DUP into non-existent memory 26 | test[0] = ass_current(); 27 | ass(O_LITERAL); lit(0xfffffff0); 28 | ass(O_SPSTORE); ass(O_DUP); ass(O_NEXT00); 29 | // test 2: set SP to MEMORY, then try to pop (>R) the stack 30 | test[1] = ass_current(); 31 | ass(O_LITERALI); ilit(R(MEMORY)); 32 | ass(O_SPSTORE); ass(O_TOR); ass(O_NEXT00); ass(O_NEXT00); 33 | // test 3: test arbitrary throw code 34 | test[2] = ass_current(); 35 | ass(O_LITERALI); ilit(42); 36 | ass(O_HALT); ass(O_NEXT00); ass(O_NEXT00); ass(O_NEXT00); 37 | // test 4: test SP can point to just after memory 38 | test[3] = ass_current(); 39 | ass(O_LITERALI); ilit(R(MEMORY)); 40 | ass(O_MINUSCELL); ass(O_SPSTORE); ass(O_TOR); ass(O_ZERO); 41 | ass(O_HALT); ass(O_NEXT00); ass(O_NEXT00); ass(O_NEXT00); 42 | // test 5: test setting SP to unaligned address 43 | test[4] = ass_current(); 44 | ass(O_ONE); ass(O_PLUSCELL); ass(O_SPSTORE); ass(O_NEXT00); 45 | // test 6: test EXECUTE on unaligned address 46 | test[5] = ass_current(); 47 | ass(O_ONE); ass(O_EXECUTE); ass(O_NEXT00); ass(O_NEXT00); 48 | // test 7: test division by zero 49 | test[6] = ass_current(); 50 | ass(O_ONE); ass(O_ZERO); ass(O_SLASH); ass(O_NEXT00); 51 | // test 8: allow execution to run off the end of memory 52 | test[7] = ass_current(); 53 | ass(O_BRANCH); ass(O_NEXT00); ass(O_NEXT00); ass(O_NEXT00); 54 | lit(R(MEMORY) - CELL_W); 55 | // test 9: fetch from an invalid address 56 | test[8] = ass_current(); 57 | ass(O_LITERAL); lit(0xffffffec); 58 | ass(O_FETCH); ass(O_NEXT00); ass(O_NEXT00); 59 | // test 10: fetch from an unaligned address 60 | test[9] = ass_current(); 61 | ass(O_ONE); ass(O_FETCH); ass(O_NEXT00); ass(O_NEXT00); 62 | // test 11: test invalid opcode 63 | test[10] = ass_current(); 64 | ass(O_UNDEFINED); ass(O_NEXT00); ass(O_NEXT00); ass(O_NEXT00); 65 | // test 12: test invalid 'THROW contents 66 | test[11] = ass_current(); 67 | ass(O_LITERAL); lit(0xffffffec); 68 | ass(O_DUP); ass(O_THROWSTORE); ass(O_THROW); 69 | 70 | start_ass(200); 71 | ass(O_HALT); 72 | 73 | R(THROW) = 200; // set address of exception handler 74 | 75 | UCELL error = 0; 76 | for (size_t i = 0; i < sizeof(test) / sizeof(test[0]); i++) { 77 | bool address_used = result[i] == EXIT_INVALID_SP || result[i] == EXIT_INVALID_THROW || 78 | result[i] == -9 || result[i] == -23; 79 | // don't test memory checking unless it is implemented 80 | if (address_used && !R(CHECKED)) 81 | continue; 82 | 83 | R(SP) = R(S0); // reset stack pointer 84 | 85 | printf("Test %zu\n", i + 1); 86 | R(EP) = test[i]; 87 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 88 | CELL res = run(); 89 | 90 | if (result[i] != res || (result[i] != 0 && bad[i] != R(BAD)) || 91 | (address_used && address[i] != R(NOT_ADDRESS))) { 92 | printf("Error in exceptions tests: test %zu failed; EP = %"PRIu32"\n", i + 1, R(EP)); 93 | printf("Return code is %d; should be %d\n", res, result[i]); 94 | if (result[i] != 0) 95 | printf("'BAD = %"PRIX32"; should be %"PRIX32"\n", R(BAD), bad[i]); 96 | if (address_used) 97 | printf("-ADDRESS = %"PRIX32"; should be %"PRIX32"\n", R(NOT_ADDRESS), address[i]); 98 | error++; 99 | } 100 | putchar('\n'); 101 | } 102 | 103 | if (error == 0) 104 | printf("Exceptions tests ran OK\n"); 105 | return error; 106 | } 107 | -------------------------------------------------------------------------------- /tests/hello.correct: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /tests/hello.txt: -------------------------------------------------------------------------------- 1 | // 0 2 | O(LITERAL)I 3 | ILITERAL 20 4 | 5 | // 4 6 | O(LITERAL)I 7 | ILITERAL 13 8 | 9 | // 8 10 | O(LITERAL)I 11 | ILITERAL 20 // STDOUT_FILENO 12 | 13 | // 12 14 | OLIB 15 | O(LITERAL)I 16 | ILITERAL 7 // WRITE-FILE 17 | 18 | // 16 19 | OLIB 20 | O0 21 | OHALT 22 | ILITERAL 0 // pad to end of word 23 | 24 | // 20 25 | BLITERAL $48 26 | BLITERAL $65 27 | BLITERAL $6c 28 | BLITERAL $6c 29 | // 24 30 | BLITERAL $6f 31 | BLITERAL $2c 32 | BLITERAL $20 33 | BLITERAL $77 34 | // 28 35 | BLITERAL $6f 36 | BLITERAL $72 37 | BLITERAL $6c 38 | BLITERAL $64 39 | // 32 40 | BLITERAL $21 41 | 42 | SAVE hello.obj 0 36 43 | QUIT 44 | -------------------------------------------------------------------------------- /tests/init.c: -------------------------------------------------------------------------------- 1 | // Test that the VM headers compile properly, and that the 2 | // storage allocation and register initialisation works. 3 | // 4 | // (c) Reuben Thomas 1994-2018 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #include "tests.h" 13 | 14 | 15 | int main(void) 16 | { 17 | int i = init(256); 18 | if (i != 0) { 19 | printf("Error in init() tests: init with valid parameters failed\n"); 20 | exit(1); 21 | } 22 | 23 | printf("init() tests ran OK\n"); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tests/lib.c: -------------------------------------------------------------------------------- 1 | // Test LIB instruction. Also uses previously-tested instructions. 2 | // FIXME: test file routines. 3 | // 4 | // (c) Reuben Thomas 1994-2020 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #include "tests.h" 13 | 14 | 15 | int main(void) 16 | { 17 | int exception = 0; 18 | CELL temp = 0; 19 | 20 | // Data for ARGC/ARGLEN/ARGCOPY tests 21 | int argc = 3; 22 | UCELL buf = 16; 23 | const char *argv[] = {"foo", "bard", "basilisk"}; 24 | 25 | init(1024); 26 | assert(register_args(argc, argv) == 0); 27 | 28 | start_ass(R(EP)); 29 | ass(O_LITERALI); ilit(16); 30 | ass(O_LIB); ilit(0); /* pad word with NEXT */ 31 | ass(O_ONE); ass(O_LITERALI); ilit(17); 32 | ass(O_LIB); ilit(0); /* pad word with NEXT */ 33 | ass(O_ONE); ass(O_LITERAL); lit(buf); ass(O_LITERALI); ilit(18); 34 | ass(O_LIB); ilit(0); /* pad word with NEXT */ 35 | 36 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 37 | 38 | assert(single_step() == EXIT_SINGLE_STEP); 39 | assert(single_step() == EXIT_SINGLE_STEP); 40 | assert(single_step() == EXIT_SINGLE_STEP); 41 | printf("argc is %"PRId32", and should be %d\n\n", LOAD_CELL(R(SP)), argc); 42 | if (POP != argc) { 43 | printf("Error in LIB tests: EP = %"PRIu32"\n", R(EP)); 44 | exit(1); 45 | } 46 | 47 | assert(single_step() == EXIT_SINGLE_STEP); 48 | assert(single_step() == EXIT_SINGLE_STEP); 49 | assert(single_step() == EXIT_SINGLE_STEP); 50 | assert(single_step() == EXIT_SINGLE_STEP); 51 | printf("arg 1's length is %"PRId32", and should be %zu\n", LOAD_CELL(R(SP)), strlen(argv[1])); 52 | if ((UCELL)POP != strlen(argv[1])) { 53 | printf("Error in LIB tests: EP = %"PRIu32"\n", R(EP)); 54 | exit(1); 55 | } 56 | 57 | assert(single_step() == EXIT_SINGLE_STEP); 58 | assert(single_step() == EXIT_SINGLE_STEP); 59 | assert(single_step() == EXIT_SINGLE_STEP); 60 | assert(single_step() == EXIT_SINGLE_STEP); 61 | printf("arg is %s, and should be %s\n", native_address_of_range(buf, 0), argv[1]); 62 | if (strncmp((char *)native_address_of_range(buf, 0), argv[1], strlen(argv[1])) != 0) { 63 | printf("Error in extra instructions tests: EP = %"PRIu32"\n", R(EP)); 64 | exit(1); 65 | } 66 | 67 | assert(exception == 0); 68 | printf("LIB tests ran OK\n"); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /tests/link.c: -------------------------------------------------------------------------------- 1 | // Test the LINK instruction. 2 | // 3 | // (c) Reuben Thomas 1995-2018 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "tests.h" 12 | 13 | 14 | int exception = 0; 15 | CELL temp; 16 | 17 | 18 | static void test(void) 19 | { 20 | PUSH(37); 21 | } 22 | 23 | int main(void) 24 | { 25 | init(4096); 26 | 27 | start_ass(R(EP)); 28 | plit(test); ass(O_LINK); ass(O_ZERO); ass(O_HALT); 29 | 30 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 31 | CELL res = run(); 32 | if (res != 0) { 33 | printf("Error in LINK tests: test aborted with return code %"PRId32"\n", res); 34 | exit(1); 35 | } 36 | 37 | printf("Top of stack is %d; should be %d\n", LOAD_CELL(R(SP)), 37); 38 | show_data_stack(); 39 | if (LOAD_CELL(R(SP)) != 37) { 40 | printf("Error in LINK tests: incorrect value on top of stack\n"); 41 | exit(1); 42 | } 43 | 44 | assert(exception == 0); 45 | printf("LINK tests ran OK\n"); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /tests/literals.c: -------------------------------------------------------------------------------- 1 | // Test the literal instructions. Also uses the NEXT instruction. 2 | // 3 | // (c) Reuben Thomas 1994-2020 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "tests.h" 12 | 13 | 14 | const char *correct[] = { "", "-257", "-257 12345678", "-257 12345678 -2" }; 15 | 16 | 17 | int main(void) 18 | { 19 | init(256); 20 | 21 | start_ass(R(EP)); 22 | ass(O_LITERAL); lit(-257); ass(O_LITERAL); lit(12345678); 23 | ass(O_LITERALI); ilit(-2); 24 | 25 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 26 | 27 | for (size_t i = 0; i - i / 5 < sizeof(correct) / sizeof(correct[0]); i++) { 28 | show_data_stack(); 29 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 30 | if (strcmp(correct[i - i / 5], val_data_stack())) { 31 | printf("Error in literals tests: EP = %"PRIu32"\n", R(EP)); 32 | exit(1); 33 | } 34 | assert(single_step() == EXIT_SINGLE_STEP); 35 | printf("I = %s\n", disass(R(I))); 36 | } 37 | 38 | printf("Literals tests ran OK\n"); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /tests/load_object.c: -------------------------------------------------------------------------------- 1 | // Test load_object(). 2 | // 3 | // (c) Reuben Thomas 1995-2018 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "tests.h" 12 | 13 | 14 | static int correct[] = { -2, -2, -1, -3, 0, 0, 0 }; 15 | 16 | 17 | static int try(char *file, UCELL address) 18 | { 19 | FILE *fp = fopen(file, "r"); 20 | int ret = load_object(fp, address); 21 | 22 | printf("load_object(\"%s\", 0) returns %d", file, ret); 23 | fclose(fp); 24 | 25 | return ret; 26 | } 27 | 28 | static char *obj_name(const char *prefix, const char *file) 29 | { 30 | char *s = malloc(strlen(prefix) + strlen(file) + 2); 31 | assert(s); 32 | strcpy(s, prefix); 33 | strcat(s, "/"); 34 | strcat(s, file); 35 | return s; 36 | } 37 | 38 | int main(int argc, char *argv[]) 39 | { 40 | const char *files[] = { 41 | "badobj1", "badobj2", "badobj3", "badobj4", "testobj1", "testobj2", "testobj3" }; 42 | char *prefix = argv[1]; 43 | int res; 44 | 45 | if (argc != 2) { 46 | printf("Usage: load_object DIRECTORY\n"); 47 | exit(1); 48 | } 49 | 50 | init(256); 51 | 52 | size_t i; 53 | for (i = 0; i < 4; i++) { 54 | char *s = obj_name(prefix, files[i]); 55 | res = try(s, 0); 56 | free(s); 57 | printf(" should be %d\n", correct[i]); 58 | if (res != correct[i]) { 59 | printf("Error in load_object() tests: file %s\n", files[i]); 60 | exit(1); 61 | } 62 | } 63 | 64 | for (; i < sizeof(files) / sizeof(files[0]); i++) { 65 | char *s = obj_name(prefix, files[i]); 66 | CELL c; 67 | res = try(s, 0); 68 | free(s); 69 | printf(" should be %d\n", correct[i]); 70 | printf("Word 0 of memory is %"PRIX32"; should be 1020304\n", (UCELL)(load_cell(0, &c), c)); 71 | if ((load_cell(0, &c), c) != 0x1020304) { 72 | printf("Error in load_object() tests: file %s\n", files[i]); 73 | exit(1); 74 | } 75 | if (res != correct[i]) { 76 | printf("Error in load_object() tests: file %s\n", files[i]); 77 | exit(1); 78 | } 79 | memset(M0, 0, R(MEMORY)); // Zero memory for next test 80 | } 81 | 82 | printf("load_object() tests ran OK\n"); 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /tests/logic.c: -------------------------------------------------------------------------------- 1 | // Test the logic operators. Also uses the NEXT and -ROT instructions. We 2 | // only test the stack handling and basic correctness of the operators here, 3 | // assuming that if the logic works in one case, it will work in all (if the 4 | // C compiler doesn't implement it correctly, we're in trouble anyway!). 5 | // 6 | // (c) Reuben Thomas 1994-2020 7 | // 8 | // The package is distributed under the GNU General Public License version 3, 9 | // or, at your option, any later version. 10 | // 11 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 12 | // RISK. 13 | 14 | #include "tests.h" 15 | 16 | 17 | const char *correct[] = { 18 | "-16777216 8 255 8", "-16777216 8 65280", 19 | "65280 -16777216 8", "65280 16711680", "16776960", "33553920", "16776960", 20 | "-16776961", "-16776961 1", "-16776961 1 -1", "-16776961 -2", "-16776962"}; 21 | 22 | 23 | int main(void) 24 | { 25 | int exception = 0; 26 | 27 | init(256); 28 | 29 | PUSH(0xff000000); PUSH(8); PUSH(0xff); PUSH(8); 30 | 31 | start_ass(R(EP)); 32 | ass(O_LSHIFT); ass(O_NROT); ass(O_RSHIFT); ass(O_OR); 33 | ass(O_LSHIFT1); ass(O_RSHIFT1); ass(O_INVERT); ass(O_ONE); 34 | ass(O_MONE); ass(O_XOR); ass(O_AND); 35 | 36 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 37 | 38 | for (size_t i = 0; i - i / 5 < sizeof(correct) / sizeof(correct[0]); i++) { 39 | show_data_stack(); 40 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 41 | if (strcmp(correct[i - i / 5], val_data_stack())) { 42 | printf("Error in logic tests: EP = %"PRIu32"\n", R(EP)); 43 | exit(1); 44 | } 45 | assert(single_step() == EXIT_SINGLE_STEP); 46 | printf("I = %s\n", disass(R(I))); 47 | } 48 | 49 | assert(exception == 0); 50 | printf("Logic tests ran OK\n"); 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /tests/memory.c: -------------------------------------------------------------------------------- 1 | // Test the memory operators. Also uses previously tested instructions. 2 | // See exceptions.c for address exception handling tests. 3 | // 4 | // (c) Reuben Thomas 1994-2020 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #include "tests.h" 13 | 14 | 15 | const char *correct[] = { 16 | "", "16384", "16380", "16380 513", "16380 513 16380", "16380", 17 | "16380 16380", "16380 513", "16380", 18 | "16380 16380", "16380 1", "16381", "2", "2 16383", "", "16380", "33554945", 19 | "", "16128", "", "16384", "", "0", "", "0", 20 | }; 21 | 22 | 23 | int main(void) 24 | { 25 | init(4096); 26 | 27 | start_ass(R(EP)); 28 | ass(O_MEMORYFETCH); ass(O_MINUSCELL); ass(O_LITERAL); lit(513); 29 | ass(O_OVER); ass(O_STORE); ass(O_DUP); ass(O_FETCH); 30 | ass(O_DROP); ass(O_DUP); ass(O_CFETCH); ass(O_PLUS); 31 | ass(O_CFETCH); ass(O_LITERAL); lit(16383); ass(O_CSTORE); ass(O_LITERAL); 32 | lit(16380); 33 | ass(O_FETCH); ass(O_DROP); ass(O_SPFETCH); ass(O_SPSTORE); 34 | ass(O_RPFETCH); ass(O_DROP); ass(O_ZERO); ass(O_RPSTORE); 35 | ass(O_RPFETCH); 36 | 37 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 38 | 39 | for (size_t i = 0; i < sizeof(correct) / sizeof(correct[0]); i++) { 40 | show_data_stack(); 41 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 42 | if (strcmp(correct[i - i / 5], val_data_stack())) { 43 | printf("Error in memory tests: EP = %"PRIu32"\n", R(EP)); 44 | exit(1); 45 | } 46 | assert(single_step() == EXIT_SINGLE_STEP); 47 | printf("I = %s\n", disass(R(I))); 48 | } 49 | 50 | printf("Memory tests ran OK\n"); 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /tests/registers.c: -------------------------------------------------------------------------------- 1 | // Test the register instructions, except for those operating on RP and SP 2 | // (see memory.c). Also uses NEXT. 3 | // 4 | // (c) Reuben Thomas 1994-2020 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #include "tests.h" 13 | 14 | 15 | #define SIZE 1024 16 | 17 | const char *correct[] = { 18 | "", str(CELL_W), "", "768", "", str(SIZE), "", 19 | "168", "", "168", "", str(SIZE), "", "-1", "-1 -1", 20 | }; 21 | 22 | 23 | int main(void) 24 | { 25 | init(SIZE / CELL_W); 26 | 27 | start_ass(R(EP)); 28 | ass(O_EPFETCH); ass(O_DROP); ass(O_S0FETCH); ass(O_DROP); 29 | ass(O_R0FETCH); ass(O_DROP); ass(O_LITERAL); ass(O_THROWSTORE); 30 | lit(168); // 42 CELLS 31 | ass(O_THROWFETCH); ass(O_DROP); ass(O_MEMORYFETCH); ass(O_DROP); 32 | ass(O_BADFETCH); ass(O_NOT_ADDRESSFETCH); 33 | 34 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 35 | 36 | for (size_t i = 0; i - i / 5 < sizeof(correct) / sizeof(correct[0]); i++) { 37 | show_data_stack(); 38 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 39 | if (strcmp(correct[i - i / 5], val_data_stack())) { 40 | printf("Error in registers tests: EP = %"PRIu32"\n", R(EP)); 41 | exit(1); 42 | } 43 | assert(single_step() == EXIT_SINGLE_STEP); 44 | printf("I = %s\n", disass(R(I))); 45 | } 46 | 47 | printf("Registers tests ran OK\n"); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /tests/run-test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run the given binary via a libtool wrapper script 3 | $LIBTOOL --mode=execute "$@" 4 | -------------------------------------------------------------------------------- /tests/run.c: -------------------------------------------------------------------------------- 1 | // Test that run works, and that the return value of the HALT instruction is 2 | // correctly returned. 3 | // 4 | // (c) Reuben Thomas 1995-2020 5 | // 6 | // The package is distributed under the GNU General Public License version 3, 7 | // or, at your option, any later version. 8 | // 9 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 10 | // RISK. 11 | 12 | #include "tests.h" 13 | 14 | 15 | int main(void) 16 | { 17 | int i = init(256); 18 | if (i != 0) { 19 | printf("Error in run() tests: init with valid parameters failed\n"); 20 | exit(1); 21 | } 22 | 23 | start_ass(52); 24 | ass(O_LITERALI); ilit(37); 25 | ass(O_HALT); 26 | 27 | assert(single_step() == EXIT_SINGLE_STEP); 28 | CELL ret = run(); 29 | 30 | printf("Return value should be 37 and is %"PRId32"\n", ret); 31 | if (ret != 37) { 32 | printf("Error in run() tests: incorrect return value from run\n"); 33 | exit(1); 34 | } 35 | 36 | printf("EP should now be 56\n"); 37 | if (R(EP) != 60) { 38 | printf("Error in run() tests: EP = %"PRIu32"\n", R(EP)); 39 | exit(1); 40 | } 41 | 42 | printf("run() tests ran OK\n"); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /tests/single_step.c: -------------------------------------------------------------------------------- 1 | // Test that single_step works. 2 | // 3 | // (c) Reuben Thomas 1994-2020 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "tests.h" 12 | 13 | 14 | int main(void) 15 | { 16 | init(256); 17 | 18 | for (int i = 0; i < 10; i++) { 19 | printf("EP = %u\n", R(EP)); 20 | assert(single_step() == EXIT_SINGLE_STEP); 21 | } 22 | 23 | printf("EP should now be 40\n"); 24 | if (R(EP) != 40) { 25 | printf("Error in single_step() tests: EP = %"PRIu32"\n", R(EP)); 26 | exit(1); 27 | } 28 | 29 | printf("single_step() tests ran OK\n"); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /tests/stack.c: -------------------------------------------------------------------------------- 1 | // Test the stack operators. Also uses the 0 and NEXT instructions. 2 | // 3 | // (c) Reuben Thomas 1994-2020 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #include "tests.h" 12 | 13 | 14 | const char *correct[] = { 15 | "1 2 3", "1 2 3 3", "1 2 3", "1 3 2", "1 3 2 3", "1 2 3 3", 16 | "1 3 2 3", "1 3 3 2 3", "1 3 3 3", "2 1 1", "2 1 2", "2 1 2 2", "1 2 2", 17 | "1 2 2 2", "2 2 1", "2 2", "2 2 1", "2 2 1 1", "2 2 1 1 1", "2 2 1 1 1 0", 18 | "2 2 1 1 1 0"}; 19 | 20 | 21 | int main(void) 22 | { 23 | int exception = 0; 24 | 25 | init(256); 26 | 27 | PUSH(1); PUSH(2); PUSH(3); // initialise the stack 28 | 29 | start_ass(R(EP)); 30 | ass(O_DUP); ass(O_DROP); ass(O_SWAP); ass(O_OVER); 31 | ass(O_ROT); ass(O_NROT); ass(O_TUCK); ass(O_NIP); 32 | ass(O_PICK); ass(O_PICK); ass(O_DUP); ass(O_ROLL); 33 | ass(O_DUP); ass(O_ROLL); ass(O_TOR); ass(O_RFETCH); 34 | ass(O_RFROM); ass(O_QDUP); ass(O_ZERO); ass(O_QDUP); 35 | 36 | assert(single_step() == EXIT_SINGLE_STEP); // load first instruction word 37 | 38 | size_t i; 39 | for (i = 0; i < 10; i++) { 40 | show_data_stack(); 41 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 42 | if (strcmp(correct[i - i / 5], val_data_stack())) { 43 | printf("Error in stack tests: EP = %"PRIu32"\n", R(EP)); 44 | exit(1); 45 | } 46 | assert(single_step() == EXIT_SINGLE_STEP); 47 | printf("I = %s\n", disass(R(I))); 48 | } 49 | 50 | R(SP) = R(S0); // reset stack 51 | PUSH(2); PUSH(1); PUSH(0); // initialise the stack 52 | printf("Next stack is wrong!\n"); 53 | 54 | size_t first = i; 55 | for (; i - i / 5 < sizeof(correct) / sizeof(correct[0]); i++) { 56 | show_data_stack(); 57 | printf("Correct stack: %s\n\n", correct[i - i / 5]); 58 | if (strcmp(correct[i - i / 5], val_data_stack()) && i != first) { 59 | printf("Error in stack tests: EP = %"PRIu32"\n", R(EP)); 60 | exit(1); 61 | } 62 | assert(single_step() == EXIT_SINGLE_STEP); 63 | printf("I = %s\n", disass(R(I))); 64 | } 65 | 66 | assert(exception == 0); 67 | printf("Stack tests ran OK\n"); 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /tests/testobj1: -------------------------------------------------------------------------------- 1 | BEETLE -------------------------------------------------------------------------------- /tests/testobj2: -------------------------------------------------------------------------------- 1 | BEETLE -------------------------------------------------------------------------------- /tests/testobj3: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env beetle 2 | BEETLE -------------------------------------------------------------------------------- /tests/tests.h: -------------------------------------------------------------------------------- 1 | // Header for VM tests. 2 | // 3 | // (c) Reuben Thomas 1995-2018 4 | // 5 | // The package is distributed under the GNU General Public License version 3, 6 | // or, at your option, any later version. 7 | // 8 | // THIS PROGRAM IS PROVIDED AS IS, WITH NO WARRANTY. USE IS AT THE USER’S 9 | // RISK. 10 | 11 | #ifndef BEETLE_TESTS 12 | #define BEETLE_TESTS 13 | 14 | 15 | #include "config.h" 16 | 17 | #include "external_syms.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "beetle.h" 25 | #include "beetle_aux.h" 26 | #include "beetle_debug.h" 27 | #include "beetle_opcodes.h" 28 | 29 | #include "stringify.h" 30 | 31 | 32 | #endif 33 | --------------------------------------------------------------------------------