2020年5月21日(米国時間)、Microsoftが自社のGW-BASICをオープンソース化し、そのソースコードを公開しました(窓の杜、ITMedia、Microsoft DevBlogs)。
ソースコードはGitHubから参照できます。すべて8086/8088のアセンブラで書かれていてなかなかに敷居の高いコードですが、軽く覗きつつ気になる所をいくつかつついてみます。
※筆者はx86系アセンブラやMicrosoftプロダクトの歴史、開発環境にはあまり詳しくないため、記事には誤りや勘違いが含まれている可能性がありますがご容赦ください。
GW-BASICとは?
Wikipediaには日本語の記事がありませんが、英語版のGW-BASICによると、IBM-PC互換機のMS-DOSにバンドルされていたBASICだそうです。日本でDOS/Vと称してIBM-PCが入ってきてメジャーになる前の話ですので、日本人にはあまりなじみがないかも知れません。
ですが、GW-BASICも、かつて日本の多くのメーカーが販売していた8bit/16bitパソコンの多くで採用されていたBASICの元である、あのMicrosoft BASICの派生の一つであり、文法的にもN-BASICやF-BASIC等、当時の日本製パソコンのBASICと非常に似通っています。
対応ハードウェアもCPUも異なりますが、公開されたソースコードを見てみることで、当時のパソコンのBASICの内部をもまた垣間見られるのではないかと思います。
まずはGWMAIN.ASM
まず、何はさておき見るべきなのは、GWMAIN.ASM というソースコードです。
冒頭のコメントを見てみると…
COMMENT *
--------- ---- -- ---- ----- --- ---- -----
COPYRIGHT 1975 BY BILL GATES AND PAUL ALLEN
--------- ---- -- ---- ----- --- ---- -----
ORIGINALLY WRITTEN ON THE PDP-10 FROM
FEBRUARY 9 TO APRIL 9 1975
BILL GATES WROTE A LOT OF STUFF.
PAUL ALLEN WROTE A LOT OF OTHER STUFF AND FAST CODE.
MONTE DAVIDOFF WROTE THE MATH PACKAGE (F4I.MAC).
*
"COPYRIGHT 1975 BY BILL GATES AND PAUL ALLEN"
おおっ、ビル・ゲイツとポール・アレンの Copyright が。ここは GW-BASIC の元となったMicrosoft BASIC に由来するコードのようで、Wikipediaの Microsoft BASIC の項目の、
マイクロソフトの基盤を築いた製品である。最初に登場したのは1975年、ホビースト向けマイクロコンピュータ・MITS Altair 8800用のAltair BASICであった。
Altair BASIC インタプリタは、マイクロソフト社を設立したポール・アレンとビル・ゲイツ(およびゲイツの同級生のモンティ・ダビドフ)が開発した。
開発にはミニコンピュータ PDP-10 を使用した。
まさにこの説明そのまんまの記述になっていて非常に興味深いです。Microsoftの原点がここにあると言えるでしょう。
また、このコメントによると 1975年2月9日~4月9日 のわずか2か月間で Microsoft BASIC が開発されたということになります。BASIC言語のコア部分だけと考えても、こんな短期間でフルアセンブラでBASICを作ってしまったとは本当にすごい。
記述言語
前述のとおり、GW-BASICはインテルの16bit CPU 8086/8088向けなので、ソースコードもこれらのアセンブリ言語で書かれていますが、よく見てみると奇妙なことに気付きます。
LEVFRE:
MOV BX,0 ;Clear error trap before inserting
MOV ONELIN,BX ; to prevent OM error from being trapped
MOV BX,VARTAB ;CURRENT END
POP SI ;XTHL
XCHG SI,BX
PUSH SI ;[H,L]=CHARACTER COUNT. VARTAB
;ONTO THE STACK
POP CX ;[B,C]=OLD VARTAB
PUSH BX ;Save count of chars to move
ADD BX,CX
コードの内容は見てのとおり8086/8088のものなのですが、なぜかコメントには"XTHL"という8080のニーモニックや、[H,L]、[B,C]といった8080のレジスタ名が出てきます。
どうやらGW-BASICのコア部分は、8080アセンブラで記述されたオリジナルのMicrosoft BASICに対して、ツールで変換を行ったコードが元になっているようです。
8086は8080とバイナリ上の互換はないものの、豊富だった8080のソフトウェア資産を移行させる目的でアセンブラソースのトランスレータが提供されていたとのことで、それが実際に使用された結果がGW-BASICのソースコードのようです。
できれば変換前の、オリジナルの8080 Microsoft BASICのソースコードも見てみたいところですが…。
*(2020/05/26追記)* @fujitanozomuさんによると、初期のMS-DOSに付属していたTRANSコマンドと変換ルールが一致しているので、これまたはこれに類するツールで変換を行ったのではないかとのこと。TRANSコマンドは以前[公開](https://gigazine.net/news/20181001-source-code-ms-dos-github/)されたMS-DOS v1.25のソースコードにもソースが含まれていたそうで、 『[伝説のDOSコマンドTRANS.COMを現代に蘇らせてZ80用のプログラムを8086用に変換してみる](https://qiita.com/fujitanozomu/items/276ec13b3f3c7078a691)』という記事でTRANSコマンドのソースコードからの再ビルドを試みられています。 トランスレータの変換ルールが明らかになっているので、8080への逆変換を行うことでオリジナルのMicrosoft BASICのソースコード(らしきもの)が再現できるかも知れません。それに意味があるかは別として(笑)。
ビルド方法は?
さて、このソースコードから再度ビルドしてバイナリを作ることはできるのでしょうか?
ソースコードのコメントによるとASM86というアセンブラが使われているようなのでそれの入手や、ビルドに必要なスクリプト等が含まれていないといった問題がありますが、そのほかにもそもそも、グラフィックスやデバイス操作の下回りについて、参照(EXTRN)されているけれど定義(PUBLIC)している箇所がどこにもないシンボルがいくつもあるようで、これらを解決しないことにはバイナリを作ることは出来ないようです。
ソースコードでは"OEM routine"などというコメントで参照されているので、これらはOEM先、GW-BASICで言えばIBMが提供するコードを利用するため、公開ができないものと思われます。
*(2020/05/26追記) @fujitanozomuさんから以下のコメントをいただきました。* > 公開されている GW-BASIC のソースを見ると明らかに MS-DOS の純正のアセンブラである MASM 用の書式で書かれており、開発途中でツールを変更して古いコメントが残っているか、あるいは MASM の名前が初期には ASM86 だったかのどちらかではないかと推察します。
GW-BASICのソースコードは結構凝ったマクロ命令の使い方をしているのですが(IBMRES.Hなどが特徴的です)、その中に出てくるディレクティブもMASMにあるものばかりなので、ソースコードのビルドに使用したのはMASMの過去のバージョンであることは間違いなさそうです。
他ターゲット対応
このソースコード自体は"GW-BASIC"のものではありますが、前述したとおり様々なハードウェアに移植されたMicrosoft BASICに由来するものであるため、ソースコードにもそうした作り分けのためと思われる記述が見られます。
例えば、
MELCO=0 ;Mitsubishi Electronics Co.
SIRIUS=0
MCI=0
ZENITH=0 ;ZENITH 8086
TETRA=0
CPM86=0
HAL=0
GENFLS=0
PANDBL=0
TSHIBA=0
SGS=0
ALPS=0
ALPCPM=0
GENWID=0
NNECBS=0
CAN8=0
PC8A=0
FN2SW=0 ;IBMTOK versions dispatch from IBMRES.MAC
LABEL=PC8A
HLPEDT=PC8A
OKI=0
BUBL=0
NORNF=0
IEESLV=0
TRSHHC=0
OLVPPC=0
NECPPC=0
USA=0 ;For HHC-USA version
EUROPE=0 ;For HHC-EUROPE version
これはGWMAIN.ASMの冒頭ですが、このように怪しいシンボル定義がいくつも出てきます。
- MELCO
- "Mitsubishi Electronics Co."と書いてあるのでそのまんまですね。当時の三菱のパソコンと言うとMULTI-8かMULTI-16あたりでしょうか…?
- TSHIBA
- 東芝っぽいですね。PASOPIAとか?
- NNECBS
- Nが二つあるのが謎ですがNECですかね? BSと言うと…TK-80BS?
- OKI
- これはまんま沖電気ですね。if800向けとかでしょうか。
- BUBL
- バブル…というと磁気バブルメモリ、と言えば富士通FM-8ですが、こいつは6809なのでまさかのBUBCOM-80???
- NECPPC
- これもPが二つあってよく分からないのですが、こちらこそまさにNECのPCシリーズ、PC-8001/8801/6001など向け? 上のPC8Aってのもそうかしら?
その他日本国外向けも、OlivettiやTandy TRS-80など聞いた覚えのあるメーカーがちらほらあって、なかなか面白いです。
ファイルを覗いてみる
さて、それでは肝心のファイルの中身を覗いてみます…とは言っても、大量にあるソースコードのどこから手を付けたらいいのか。そこで助けになるのが、各ソースコードの冒頭に出てくるTITLEディレクティブです。ここには
TITLE BIBOOT - Initialization File for ASM86 BASICs
こんな感じでそのソースコードの内容が1行説明で書かれているので、それを元にソースファイルをざっくりいくつかに分類しながら見てみます。
BASIC言語処理
- BIBIOOT.ASM
- Initialization File for ASM86 BASICs
BASICの起動処理です。コメントによるとMS-DOS起動時のメモリとセグメントレジスタの状態がBASICが期待しているものと違っていてそれを修正してBASIC本体に飛んでいる様子?
ここからなぜかGWDATA.ASMのSTARTというシンボルに飛んだ後、そこでは何もせずにGWINIT.ASMのINITに飛んで、実際のBASICの初期化が始まります。
- BIMISC.ASM
- BASIC Interpreter miscellaneous routines/WHG/PGA etc.
名前の通りの雑多な処理のようですが、NEW
やCLEAR
などもここでやっているようです。
- IBMRES.ASM
- IBM compatible reserved words / MLC
- IBMRES.H
BASICの予約語がここで定義されています。なぜ"IBM"なのか謎ですが、サポートする予約語がOEM先によってまちまちなのでソースコードごと別に分けていたと想像。
- GWDATA.ASM
- copied from BINTRP.MAC
言語処理で使われるさまざまなデータ領域がある他、各種エラーメッセージの文字列がここで定義されています。おなじみ"Syntax error"や"Illegal function call"などの他、PC-6001など一部のBASICで見られる"?SN ERROR"といった2文字表記も併せて定義されていて、どうやらターゲットごとにどちらを使うか切り替えられるようになっている様子。
- GWEVAL.ASM
- Copied from BINTRP.MAC
"eval"の通り、BASICの命令中に登場する式の評価を行う処理らしい。
- GWINIT.ASM
- GW-BASIC-86 Initialization
BASIC起動時の初期化処理。OSから渡されたコマンドラインの解釈などもここでやっている様子。
- GWLIST.ASM
- Copied from BINTRP.MAC
BASICの"LIST"コマンドの処理。メモリ上に中間コードで置かれているコードを元に戻して表示するような処理をやっているのだと思います。
- GWMAIN.ASM
- Copied from BINTRP.MAC
はい来ましたBASICのメイン処理。プログラムをRUNして1行ずつ実行していく処理や、FOR
~NEXT
やGOSUB
~RETURN
などのプログラムの流れを変える処理などをやっている様子。
- NEXT86.ASM
FOR
~NEXT
のNEXT
文の処理だけなぜかこちらにあります。
-
GWRAM.ASM
-
GW BASIC OEM Independent RAM Declarations
-
GWSTS.ASM
-
GW-BASIC Common Statement Support
いろんな命令、関数の処理が並んでいるらしい。PLAY命令があるのに注目。MML(Music Macro Language)の処理などもここに入っているようです。
- BIPRTU.ASM
- BASIC Interpreter PRINT USING Driver/WHG
これはコメントそのままで、BASIC言語での書式付出力命令のPRINT USINGの処理。
- BIPTRG.ASM
- BASIC Interpreter pointer get routines/WHG/PGA
BASICでポインタとは?と思ったら、どうやら変数や配列の格納アドレスのポインタを得る処理らしい。
- BISTRS.ASM
- BASIC Interpreter String routines/WHG/PGA etc.
文字列変数を扱う部分。LEN()
,ASC()
,CHR$()
,LEFT$()
,RIGHT$()
,MID$()
などBASICでおなじみの文字列関数の他、変数領域のガーベージコレクションなどもやっている様子。
数値演算処理
- MATH1.ASM
- MATH86 8086 MATH PACK
- MATH2.ASM
BASICでサポートしている、整数や単精度/倍精度浮動小数点数の数値演算処理を行うためのコードです。
現在のCPUであれば浮動小数点数の四則演算程度はFPU命令で一発ですが、Microsoft BASICが動いていた8080にはもちろんそんなものはなく、また8086/8088にはコプロセッサとして8087というFPUはありましたがオプション扱いだったので、すべての演算処理をCPUの整数演算命令のみで実現しています。四則演算だけでなく、BASICでサポートされている三角関数やSQR()
,LOG()
,EXP()
などの関数もここで用意されています。
現在は、浮動小数点演算を行う場合のフォーマットはほぼ [IEEE754]
(https://ja.wikipedia.org/wiki/IEEE_754) という標準規格で統一されていますが、当時は規格策定前ということもあり、似て非なるフォーマットが使われています。
フォーマットについてはソースコード MATH1.ASM 中のコメントで説明されています。
単精度浮動小数点数は以下の通りです。
;IS THE EXPONENT OF THE LARGER NUMBER. THE FORMAT OF
;FLOATING POINT NUMBERS IS AS FOLLOWS:
;
;BIT 33222222 22221111 11111100 00000000
; 10987654 32109876 54321098 76543210
; AAAAAAAA BCCCCCCC CCCCCCCC CCCCCCCC
;BYTE [ $FAC ] [$FAC-1] [$FAC-2] [$FAC-3]
; [$FACLO]
;
;WHERE A=BITS OF EXPONENT BIASED BY 128
; B=0 IF NUMBER IS POSITIVE,1 IF NEGATIVE
; C=BITS 2-24 OF MANTISSA(BIT 1 IS UNDERSTOOD 1)
;NOTE:THE BINARY POINT IS TO THE LEFT OF THE UNDERSTOOD 1
指数部が8bit、符号が1bit、仮数部が24bit(うち1bitは省略)というビットの割り当てはIEEE754 単精度浮動小数点数と同じですが、
- 符号ビットの位置が異なる
- Microsoftの方式は指数部が最初の1バイトに収まっているので指数部を抜き出す処理が楽な一方で、IEEE754は符号が最上位bitなので正負の判断がやりやすいという特徴があります。
- IEEE754に見られる無限大・無限小(Inf)、非数(NaN)の扱いがない
- BASIC言語的には演算中に出てきたこれらの値は即座にエラーにするため、数値データとして扱う必要がないためと思われます。
- 指数部に加えるバイアスの値が異なる
- IEEE754では127を足しますが、Microsoft方式は128を足します。前述のとおりInfやNaNを扱わないため、それを表す指数(255)も数値として扱えるためと思われます。
などの違いがあるようです。
倍精度浮動小数点数は以下のコメントの通りです。
;THE FORMAT OF DOUBLE PRECISION NUMBERS IS AS FOLLOWS
;;
;BIT:
;66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000
;32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210
;AAAAAAAA BCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCC
;[$FAC ] [$FAC-1] [$FAC-2] [$FACLO] [$DFACL [$DFACL [$DFACL [$DFACL]
; +3 ] +2 ] +1 ]
;
;WHERE A=EXPONENT BIASED 128
; B=SIGN(1=NEGATIVE,0=POSITIVE) OF NUMBER
; C=BITS 2-56 OF MANTISSA (BIT 1 IS UNDERSTOOD 1)
; (ALSO BIT 54 IS HIGH ORDER BIT)
;NOTE:THE BINARY POINT IS TO THE LEFT OF THE UNDERSTOOD 1
こちらはIEEE754 倍精度と異なり、単に単精度の仮数部の精度を24bitから56bitに拡張しただけとなっています。
扱える値の範囲はIEEE754より劣る(というか単精度と同じ)ですが、演算精度の上ではIEEE754より上になります。
このフォーマットについては、英語版のWikipediaの Microsoft Binary Format に説明があります。N-BASICやF-BASICなど、Microsoft BASIC由来の国産パソコンの数値表現もおおむねこれだったはずです。
グラフィックス関連
- GENGRP.ASM
- GENERALIZED GRAPHICS /WHG
- ADVGRP.ASM
- ADVANCED GENERALIZED GRAPHICS STUFF
PSET(x,y)
やPRESET(x,y)
、LINE(x1,y1)-(x2,y2)
といった懐かしいグラフィック命令の処理がここです。ASVGRP.ASMの方にはシードフィル(PAINT(x,y)
)や円描画CIRCLE(x,y),r
、GET@
やPUT@
などの処理が。
残念ながら、グラフィック処理の下回りの多くの部分はOEM routineということで公開されているソースには含まれていないようですが。
I/O関連
- GIOTBL.ASM
- Device Name Table, Dispatch Table Address Tables
- GIOCOM.ASM
- Communications Machine Independent Device Driver Code
- GIOCON.ASM
- Machine Independent CONS: Device Support
- GIODSK.ASM
- BASIC-86 Generalized I/O Disk Driver
- GIOKYB.ASM
- Machine Independent Keyboard Device Driver Code
- GIOLPT.ASM
- Line Printer Machine Independent Device Driver Code
- GIOSCN.ASM
- Screen Machine Independent Device Driver Code
COM(シリアルポート)、CON(コンソール)、ディスク上のファイル、キーボード、プリンタといった各種デバイスのI/O処理があります。GIODSK.ASMにはFILES
やKILL
などのファイルシステム操作命令の処理も入っています。
スクリーンエディタ
- SCNDRV.ASM
- This is the OS independent Screen Driver for GW BASIC
- SCNEDT.ASM
- Screen Oriented Editor for GW-BASIC
当時のBASIC言語で特徴的な、LISTで表示したソースコードを画面上でそのまま編集して改行を打つと更新されるというスクリーンエディタの機能がここで行われているようです。
その他
- KANJ86.ASM
- KANJI String Function Support for Basic-86
なんと漢字処理が!と思いきや、中身はほぼ空っぽです。
- CALL86.ASM
- 8086 CALL Statement
どうやらGW-BASICには機械語コードを直接呼び出すCALL
という命令があったようで、その処理のようです。
- DSKCOM.ASM
- COMMON ROUTINES FOR DISK BASICS
Disk BASICでサポートされたコマンドの処理のようです。
- FIVEO.ASM
- 5.0 Features -WHILE/WEND, CALL, CHAIN, WRITE /P. Allen
5.0とは何ぞや?と思いますが、version 5.0で追加されたこれらの命令のサポートというところでしょうか。
- GIO86.ASM
- BASIC-86 Interpreter Device Independent I/O Module
OPEN
,CLOSE
などのファイルI/O命令の処理など。
- GIOCAS.ASM
- Cassette Machine Independent Device Driver Code
なんとカセット関係の処理が。と言っても中身はほぼ空で、MOTOR
コマンドの痕跡だけ残っています。
- ITSA86.ASM
- Resident Initialization for I8086
メモリ周りの初期化処理のようです。
- MACLNG.ASM
- MACRO LANGUAGE DRIVER
コメントに"MICROSOFT GRAPHICS AND SOUND MACRO LANGUAGE"とありますが詳細不明。
- BINTRP.H
- GIO86U
- MSDOSU
- OEM.H
終わりに
というわけで分量は多いし公開されていないソースもあったりしてなかなか一筋縄では行きませんが、「あの」Microsoft BASIC由来のBASICのソースコードという、歴史的にはかなり価値のあるものが自由に利用できるようになりました。まだ上っ面を覗いてみただけに過ぎない状態ですが、もうすこしBASICインタプリタ処理の細部に潜ってみたいところですねえ…。