23

It was common with Commodore BASIC (and others, I'm sure) to have machine language encoded in BASIC programs using POKEs or READ/DATA. This was needed for both performance and to access certain machine functions not accessible from BASIC syntax.

Of course, this was very sub-optimal, since it required an external assembler, provided no simple data sharing mechanism, and the code was completely unreadable.

One obvious "better way" is to embed actual assembly language code that can easily share variables, parameters, and return values with BASIC. Though I'm unfamiliar with exactly how it worked, the BBC BASIC allows this technique.

Did any other 8-bit micros have BASIC interpreters supporting inline assembly, or some other technique that could make mixing machine and BASIC code more practical AND readable?


Moderator's Note:

Please refrain from posting new answers to this question; instead add information to the Community Wiki answer if it is not there already.

11
  • 10
    The “external assembler” was usually just a pencil and paper ;-). Jan 19, 2018 at 18:52
  • 6
    '''The obvious "better way" is to embed actual assembly language code''' ... which obviously will need more space than externally assembled machine code. "Obvious better" depends a lot on from which side you look at that problem.
    – user28
    Jan 19, 2018 at 19:13
  • 5
    I'm not so sure as well if "obvious better" actually hits the spot here - Embedding assembler code means the code needs to be assembled every time the program runs which is nice for development, but not quite nice for the user who needs to wait until the code is generated.
    – tofro
    Jan 19, 2018 at 19:26
  • 1
    I purposely put "better way" in quotes to solicit answers that could teach me other alternatives for easily mixing BASIC and machine code.
    – Brian H
    Jan 19, 2018 at 20:08
  • 1
    Going the other way, PAL was an assembler with support for hybrid programming in BASIC on Commodore computers.
    – user722
    Jan 19, 2018 at 21:06

8 Answers 8

26

BBC BASIC: inline assembly

First one that comes to mind would be the BBC BASIC family.

Beside many great features to access the OS, Assembly code could be directly inserted. It would be assembled right between the BASIC lines before and after.

10 P%=8000 : REM set Assembly location
20 [
30  LDA #1
30  JSR some_function
40  RTS
50 ]
60 CALL 8000 : REM now execute it

The feature originally started out with Acorn's Atom, followed by the BBC Micro (Acorn Proton) and it's offsprings (all the way to the Electron). BBC BASIC as it was called by now was ported from the 6502 to other popular CPUs, foremost of course to all the ones supported by BBC Micro Tube modules, like Z80, NS32016 or ARM (Standard BASIC for RiscOS).

Especially the Z80 Version became wide spread as machines like Sinclair's Z88 (as Tofro mentioned) or Amstrad's N100/200 series did feature it - now of course supporting Zilog syntax :))

In more recent years it also has been ported (and extended) for Windows and TI 83 calculators (also Z80 based).

AFAICT it's still under somewhat active development by its original creators.

There are also several new(er) reimplementations available, so it might be a good idea to search a bit for the desired machine/OS (like Linux).

Amstrad CPC: Resident System Extension

As comments on question pointed out, inline assembly source consumes much more space than just machine code bytes, plus assembly time.

A memory and time efficient compromise

One can reap those other benefits mentioned in question: (1) memory-efficient, (2) time-efficient, (3) information sharing/exchange with basic.

One option is basically to consider assembly routines a kind of library or system extension, rather than part of BASIC program. With proper calling mechanisms, such libraries can be used from basic very conveniently. They are just developed with separate tool, not directly inside BASIC source.

How Amstrad did it

That's the approach taken by Amstrad when developing the CPC 464. Amstrad BASIC allows to register extension routines made of Z80 machine code. BASIC can then call them exchanging typed parameters: integers passed as values or pointers, strings as pointers. This allows changing BASIC variables (kind of multiple return values).

It can be used with libraries supplied as ROM or loadable into RAM from tape or disc. Short reference: RSX on CPCWiki

Actual example of BASIC call

One can program this:

10 memory &9000 : LOAD"extensio.bin" : call &9000 'register RSX
20 a$="mystring" : |myuppercasecommand,@a$ : print a$ 'would print MYSTRING
30 |mywhatevercommand,myintegerargument1,@myargument2passedaspointer

Amstrad used it for their extensions

Disc drive

This was convenient enough that Amstrad used this very mechanism to provide BASIC access to hardware extensions like the floppy disc drive with functions like |DISC, |TAPE, |REN,"oldname.bad","newname.bas", etc (details: AMSDOS on CPCWiki).

Hardware-based speech synthesizer

For example, the Amstrad SSA-1 Speech Synthesizer provided convenient routines callable from BASIC. This would be a valid BASIC program using speech synthetizer:

10 ' User has to first run program supplied by speech synthetizer manufacturer.
20 |say,"Hello, what is your name?"
30 input name$
40 r$="Pleased to meet you " + name$ + "." : |say,@r$

Official documentation

To create and register personal extensions, Amstrad provided an assembly-level API , documented in SOFT 968 Section 10: Expansion ROMs, Resident System Extensions and RAM Programs.

Extract:

This example uses an invented external command which takes a string of characters, looks these up in an index and returns a reference number. The external command is assumed to be designed to be called from BASIC as follows:

|REFNUM,@CHARTRING$,INDEXNUM,@REFNUM

i.e. The first parameter is a string (whose address is passed) which is to be looked up. The second parameter is a number specifying which index to use, and the third parameter is a variable (whose address is passed) which is to be set to the required reference number.

Apple II: Monitor, Mini-Assembler, Ampersand, USR

While the Monitor and Mini-Assembler were not technically part of BASIC, they were available in ROM and machine language code they produced could easily be run from BASIC.

The Monitor

From BASIC, you could access the monitor with the ubiquitous "CALL -151" command. It would also come up if a BRK instruction was executed. The system monitor provided direct access to system memory (including ROM and memory-mapped hardware) and various features useful for directly manipulating memory. The monitor contained a disassembler which could be accessed by inputting an address followed by 'L', e.g. "300L" will disassemble the code located at (hexadecimal) address 300. You could continue on disassembling more code just by typing 'L', as the monitor remembers the last address it was operating on. Simply typing an address with no command would dump the memory at that location in hexadecimal form. The monitor also saved and displayed the CPU registers (and restored them before executing a command) and also supported an 'S' command which single-stepped through an assembly program, providing actually rather useful debugging features.

Within the monitor, you could then access the mini-assembler with the command "F666G" (nobody said these commands would be easy to remember). The "G" command tells the monitor to run the program located at the specified address, so "F666G" simply runs the program at that location, which happens to be the mini-assembler. If you loaded the mini-assembler from disk you instead would give the address of wherever in memory you put it. The //e and later allowed you to enter the mini-assembler with the simpler command "!" (which happened also to be the prompt character for it), which also allowed them to move the code.

The Mini-Assembler

While not technically part of BASIC, the Apple II family supported a mini-assembler, which was created by Steve Wozniak himself. The mini-assembler was included in the original Apple II ROM with Integer BASIC (supposedly, Woz wrote Integer BASIC itself using the mini-assembler). Systems with Applesoft in ROM lost the mini-assembler, but it later returned in the Enhanced //e, //c, //c+ and IIgs. Those computers without the mini-assembler in ROM could load it from disk or tape. All versions of the Apple II had a disassembler in ROM.

The mini-assembler provided a minimalistic interpretation of assembly language. While it supported all the instructions and addressing modes available on the installed CPU, it had few other features. Features not found in the mini-assembler but that were common in more full-featured assemblers included labels, code relocation, pseudo-opcodes, decimal numbers, embedded data, or much of anything else besides "[address] mnemonic operand". It did allow you to pass commands to the monitor, so you had not only assembler but memory poke/dump/disassembler/debugger available from the same command line. Errors would produce a beep and a printout of the input that was in error, and you exited by entering a simple blank line.

The Ampersand

Applesoft BASIC also had an unusual ampersand command which, while not originally intended for public consumption, eventually became a more efficient way of calling machine-language code than the usual DATA/POKE/CALL commands. The ampersand hook allowed the programmer to attach machine-language code to an Applesoft program, where it would be managed by Applesoft and loaded/saved along with the rest of the BASIC program. Data could be passed to the ampersand command directly from BASIC, and could not only assign variables, but even modify the language itself - adding additional flow control constructs, for example. While this was nice for the BASIC programmer it was more work for the assembly programmer. In this way the extra code functioned more like a language extension than a specific assembly-language routine, and the additional code didn't appear in the BASIC program listing. (While DATA statements containing assembly routines aren't particularly easy to understand, they're right there and you can at least see them).

USR()

USR() was like CALL, but it could pass and return a value (in the accumulator). Like CALL, additional data could be passed in free zero-page locations, the keyboard buffer, or some other transient storage. Unlike CALL, you could only have ONE routine attached to the USR hook, so it is really only useful in limited circumstances.

Commodore C128 (CBM BASIC 7.0)

BASIC 7.0, the vastly improved BASIC in 128 mode, has some features that simplify the process of combining BASIC and ML.

In addition to calling an ML routine, the SYS statement can also pass values from BASIC to ML. The values must be in the range 0-255 and are placed in the microprocessor's registers just before the ML routine takes over. Simply tack them onto the end of the SYS command, separated by commas. Conversely, the RREG command lets you read the processor's registers from BASIC after an ML routine has finished.

The BLOAD command can bring in any ML module with no fuss or bother. The file loads into the same memory area from which it was saved, and BASIC continues with the next command. This is much simpler than the gyrations required in earlier versions of Commodore BASIC.

(please add other Machines/Basics and their workings by editing this answer)

10
  • 1
    And these were the most famous and actually pretty good inline assemblers
    – tofro
    Jan 19, 2018 at 18:49
  • 1
    @tofro yeah. Still I think there where others also. Not as perfect as tehBBC, but better than just poking. And no, I'm not talking about Assemblers that did transform PET BASIC into translating Assembly lines embedded after the programm. Quite nifty, but something else.
    – Raffzahn
    Jan 19, 2018 at 20:36
  • I unmarked as a CW question because it had side-effects.
    – wizzwizz4
    Jan 20, 2018 at 13:12
  • 1
    @Raffzahn Inability to vote on the question... or edit the question. Also lack of edit links for anonymous users for even this CW answer. (It's a wiki-lock, which is internally "lock minus no answers" - not good.)
    – wizzwizz4
    Jan 20, 2018 at 13:25
  • 1
    @Raffzahn It will not assemble and call in a single operation. You have to explicitly call the machine code, setting up parameters as necessary (either via A%-Z% integer variables or, on later versions of BBC BASIC, using parameters to CALL (ie CALL start_address%, parameter1, parameter2$). You can even assemble single instructions at the > command prompt, eg: > P%=&2000 > [ ADC #10 2000 69 0A ADC #10 > [ RTS 2002 60 RTS > CALL &2000 (Tested with JSBeeb. The machine code instructions generated are 69 0A 60) Jan 21, 2018 at 22:43
8

It is not commonly known, but the Cambridge Z88 also had BBC Basic. And supported inline assembly. For the Z80, obviously, and not 6502.

A little gem of a program named ZX-IDE allows mixed-mode Basic and assembler cross-development for the ZX-81 on a Windows PC. It is directly integrated with the EIghty-One emulator for lightning-fast turnaround.

http://www.sinclairzxworld.com/viewtopic.php?t=1064

5
  • Right. BBC Basic was ported to a whole bunch of architectures: there were versions available for CP/M and a variety of other Z80 based machines prior to the Z88 - which I believe was the only Z80 machine to use it in its ROM, and for 8086-based machines including both IBM PC and RM Nimbus, the latter of which was a more exact replica of the original BBC version as it specifically had BBC Micro compatible graphics modes designed into the hardware. In all of them, there was an appropriate assembler for the native architecture.
    – Jules
    Jan 19, 2018 at 19:14
  • Well, wasn't the question about assembler? Also, puting machine code into REMs isn't anything special to the ZX80. That was common on next to all BASIC machine.
    – Raffzahn
    Jan 19, 2018 at 19:59
  • With the tube processors, there was probably also BBC BASIC (presumably with an assembler?) for the 32016?
    – Tommy
    Jan 19, 2018 at 20:04
  • 1
    Also, re ZX models, I think part of that might be because the ZX80 and ZX81 don't have the equivalent of the Spectrum's LOAD x CODE — they can only load and save an entire memory dump, which is taken to be a BASIC program. So there is no better solution than a REM-prefixed block for transporting data blobs in a way that the ROM can load.
    – Tommy
    Jan 19, 2018 at 20:10
  • @Jules - the Amstrad NC series had BBC BASIC (Z80) in ROM too. Having a built-in assembler was a design goal for all BBC BASIC interpreters, and where possible, Richard Russell's ones have included it for x86 and ARM (Linux). Sophie's interpreter for RISC OS also provided an assembler, but for an older ARM dialect. Atom BASIC, the precursor to BBC BASIC from 1980, included an assembler
    – scruss
    Sep 23, 2022 at 16:19
6

Microsoft BASIC (MBASIC) supported the CALL/USR functions, where you could call non-BASIC functions located at a specified address. If you used the extended/disk versions, there were several ways of calling assembly without too much pain.

From section 2.2:

110 MYROUT=&HD000 
120 CALL MYROUT(I,J,K)

From section 2.13:

200 DEF USR0=24000 
210 X=USR0(Y^2/2-89)

DEF USR had the interesting property that you could rebind each of the USR variables, allowing for indirection.

Instead of having to use POKE or DATA/READ, you could also use VARPTR to get the starting address of a file buffer or string array: You then used that computed address to set the USRx or CALL variable (the BASIC compiler allowed you to link assembly functions directly into your program and then use CALL without having to specify the starting address.)

From section 3.43:

100 X=USR(VARPTR(Y))

Later, Microsoft added BLOAD which allowed you to load in binary data more quickly (I won't say that the speed was impressive, because it wasn't even then!) However, I'm not certain that any 8-bit versions of Microsoft's BASIC included that command.

2
  • @wizzwizz4 Actually the question is asking about easy techniques for using assembly code from BASIC, with inlining mentioned as one nice solution, but not necessarily the only way.
    – Brian H
    Jan 20, 2018 at 15:54
  • You're right. Yes, I think this qualifies as "readable" so it gets an upvote.
    – wizzwizz4
    Jan 20, 2018 at 17:08
5

Acorn computers did

Acorn Atom had Atom Basic which could have 6502 assembler.

The later BBC Micro which was 6502 based had BBC Basic (later versions for Intel are here Wikipedia description

4
  • There are a couple of typos... but apart from that this is a good answer. Welcome to Retrocomputing.
    – wizzwizz4
    Jan 19, 2018 at 20:46
  • @Mick I think the "could have" means "could be used with" as opposed to "might have the feature" (mostly because you've both got the same links).
    – wizzwizz4
    Jan 19, 2018 at 20:46
  • Because the BBC assembler was so great, some Oric games (Xenon, Zorgon's revenge) were developped using BBC assembler and a special cable & program to transfer assembled data to the target... Sep 23, 2022 at 14:59
4

It is important to remember that most BASIC computers run an interpreter. So having to compile assembler at run time would have not been desirable (and, as the main reason to use it was always speed and/or RAM, interpreting it would have not been acceptable).

Also, a processor like Z80 (the one I knew best, using it on TRS80 Models I and III) had a simple enough set of instructions that one started memorizing the opcodes fairly quickly. I wrote a disassembler in BASIC when I was 14, but by the time I was in position to use it to disassemble significant code, I was proficient enough in reading in hexadecimal that most of the time I would not need it. With that knowledge, assembling a short routine by hand was not a big deal.

In case one needed a more substantial part of the program in assembler, one could produce a separate executable and run it from BASIC, or write a short BASIC routine that would poke the program.

3
  • Yeah, same here. I was with my tape deck kaput most of the time, and was quite proficient input code machine via hex in BASIC in my ZX Spectrum. Jan 23, 2018 at 16:44
  • I don't see how this answers the question.
    – nekomatic
    Feb 6, 2018 at 15:19
  • Assembling at run time was certainly desirable when developing. And in those days a high proportion of users did program their computers. If you're talking about simply using a commercial program, then not only was assembling at run time undesirable, so was writing in BASIC.Such programs would usually be shipped as pure machine code, which often had been developed on the BBC BASIC built in assembler. Feb 11, 2021 at 14:47
3

Blitz Basic II on the Amiga supported inline assembler. More commonly you would use an assembler function. It was a proper assembler, no need to use data statements or pokes.

2

The only microcomputer BASIC I've ever seen which did not provide any mechanism for running machine code was the built-in interpreter of the TI 99/4 and TI 99/4a. So far as I can tell, that machine required either a machine-language monitor or extended-BASIC cartridge to even load machine code programs off tape. It's possible that someone has by now discovered a way to "jail-break" it from within a BASIC program, but many cassette-based games back in the day required that the user have one of those cartridges--a requirement I don't think they would have imposed if they could avoid it.

5
  • 1
    Then again,the need for assembly additions was way lower than on other machines due the rather high quality of available functions. The 99/4 was one of the few machines of that time frame that allowed acceptable game performance from straight BASIC.
    – Raffzahn
    Jan 19, 2018 at 21:42
  • 3
    @Raffzahn: That was true when using Extended BASIC. The performance of the built-in BASIC was really bad.
    – supercat
    Jan 19, 2018 at 21:53
  • 2
    AIUI a jailbreak of the standard TI-BASIC in order to run non-trivial machine code is actually basically impossible, due to the odd RAM organisation of the machine: it only has a very small amount of RAM actually connected to the processor (128x16-bit words IIRC) and the rest of the memory is hooked up via an interface to the graphics processor. The CPU cannot execute code in the graphics processor memory. In order to execute code, you need a memory expansion; the memory expansion came with a ROM that added extra call ... subprograms to BASIC for peek/poke/execute operations.
    – Jules
    Jan 20, 2018 at 11:24
  • @Jules: Did both the extended-basic and machine-language monitor carts contain RAM, then? Also, even if BASIC is horribly slow, I would think a program that was written in GPL could be faster. Not as fast as one written in machine code, but perhaps faster nonetheless.
    – supercat
    Jan 20, 2018 at 17:51
  • @supercat - Extended BASIC only allowed machine code programs in presence of a separate memory expansion (see page 1 of the manual). I've never played with GPL, though, so know little about it -- I do believe you're correct that it was faster than BASIC though (I've seen comparisons that suggest it had similar performance to the UCSD Pascal p-code interpreter that was also available for the machine).
    – Jules
    Jan 23, 2018 at 22:52
1

Any BASIC with a BRUN or SYS command could load a separate assembly program into memory and transfer control to that.

Also, the hex or decimal for assembly opcodes as used in DATA statements was as familiar as the mneumonic after awhile.

2
  • 1
    Yes, that is useful. I was thinking more along the lines of applications written mainly in BASIC, with performance critical or machine-specific I/O routines coded in assembly. How would you pre-load those "device driver" routines in BASIC and how would you manage parameters passing and return values in a clean, efficient, hopefully readable way?
    – Brian H
    Jan 19, 2018 at 21:43
  • 1
    In my TRS-80 and C64 days I wrapped my assembler parts as own BASIC extensions having own keywords and using the routines of the interpreters to get the arguments. In the TRS-80's Level2 BASIC (optionally with disk extensions) that could done by hooking into the error vector. Own keywords would not have tokens that way. E.G. "MASKINPUT" would be causing an error and you'd get a pointer to where the string "MASK" plus the token for "INPUT" triggered the error in your program. My memories how I did it on the C64 faded even more than the TRS-80 memories... :-(
    – user28
    Jan 20, 2018 at 4:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .