プログラミング
移植性のないトランスピュータCコンパイラを移植可能にする
Making portable my unportable transputer C compiler (nanochess.org)
要約
著者は1998年当時のトランスピュータ用Cコンパイラを現代の64ビットマシンでコンパイルしようとしたが、ポインタと整数型の混在、プロトタイプ宣言の欠如、Small-Cからの引き継ぎ問題など、多くの移植性の問題に直面した。これらの問題を解決するため、コードの構造を改善し、型定義の扱いを変更するなどの設計変更を行った。
全文翻訳
メインページ Intel 8080エミュレータ チェスプログラム コンテスト ストア レトロゲーミング FAQ リンク 私について スペイン語で見る
移植性のないトランスピュータCコンパイラを移植可能にする
Oscar Toledo G. 著 2026年9月21日
数週間前、トランスピュータ用の最新Cコンパイラが入ったフロッピーディスクを見つけました。これはDelorieのG++またはDJGPP(1998年頃)でコンパイルできるように強化されていました。もちろん、コンパイル時に多くの警告が発生しましたが、32ビットのIntel 80486プロセッサ上でコンパイルされていた32ビットコンパイラだったので、問題なく動作しました。これはAMD Am29000プロセッサ用のコード生成に変換する前の中間ステップであり、このバージョンはVestigial Small-Cの構造を取り除き、適切な字句解析器、ANSI C構文、および完全なプリプロセッサを持つようにさらに改善されました。
懐古的な演習として、1998年版のトランスピュータCコンパイラを現代の64ビットコンピュータ、Macbook Air M1でコンパイルしようとしましたが、多くの困難に遭遇しました。例えば:
コードは整数とポインタを相互に(両方とも32ビットでした)使用しています
FILE *型は使用されておらず、代わりにintが使用されていました
コンパイラ関数のプロトタイプがないため、clangはポインタが64ビットで整数が32ビットであるため、正当な理由で不平を言いました。
これらの移植性の問題の一部はSmall-Cから引き継がれました。structキーワードがなかったため、必要なすべての構造体はバイトプール(char)で作成され、単語は2バイト(8080プロセッサ用)に分割され、32ビットプラットフォーム用に拡張したとき、これらの単語は4バイトになりました。さらに悪いことに、ポインタはint型に変換され、int型から変換されますが、64ビットポインタを32ビット整数に変換することは不可能になりました。設計変更が必要です!
Small-Cコードの断片で、値が配列に2バイトとして保存されています。
最初のステップ
古いプログラムが現代の64ビットシステムでコンパイルできなくなるのは、これが初めてではありません。しかし、このコンパイラをエミュレーションでのみ動作する好奇心の対象として放置するのではなく、あなたの現代のラップトップで動作するようにしましょう。ネタバレ:簡単ではありませんでした。
私は目標を設定しました。コンパイラを現代の64ビットプラットフォームでコンパイルできる程度にのみ変更し、それでもトランスピュータ用にコンパイルできるようにしたいと思いました。これは、私のコンパイラに実装されているものしか使用できないことを意味します。
コンパイラはいくつかのソースファイルに分割されており、すべてcc.cという単一のドライバファイルから呼び出されます。私のDJGPPポートは2つの異なるファイルを作成しました:cc.cとcc2.c。最初のファイルはDJGPPでコンパイルされることを意図しており、2番目のファイルは私のCコンパイラで直接トランスピュータ上でコンパイルされることを意図していました。
私は変数entrada、salida、およびentrada2をこれらのメインファイルに移動することから始めました。現代のマシン用のファイルをFILE *を使用するように変更し、またKernighan & Ritchie関数のプロトタイプを追加し始めました。K&Rプロトタイプは正しい戻り型を示すためだけに使用されます。例えば、unsigned char *expresion();。
楽しい部分を始めましょう
#includeディレクティブは、ソースファイル内の処理の現在の状態を保存します。それは次のように行われます。
incl[nivel_incl++] = entrada;
incl[nivel_incl++] = funcion_actual;
incl[nivel_incl++] = comienzo_funcion;
incl[nivel_incl++] = linea_actual;
incl[nivel_incl++] = dentro_funcion;
ここでinclは整数配列です。最初の行は現在のファイル用、次の行は関数定義へのポインタ、残りの3行は整数です。大きな問題が見えますか?64ビットマシンは64ビットポインタを持っていますが、intはまだ32ビットです。また、コンパイラのソースコード全体はまだスペイン語のコメントと変数名を使用しています。
私はコードを構造体で書き直しました。
struct {
FILE *entrada;
unsigned char *funcion_actual;
int comienzo_funcion;
int linea_actual;
int dentro_funcion;
} incl[MAX_INCL];
そして、コードの新しいバージョンは移植可能であり、かなりクリーンになりました。
incl[nivel_incl].entrada = entrada;
incl[nivel_incl].funcion_actual = funcion_actual;
incl[nivel_incl].comienzo_funcion = comienzo_funcion;
incl[nivel_incl].linea_actual = linea_actual;
incl[nivel_incl].dentro_funcion = dentro_funcion;
nivel_incl++;
設計が進むにつれて、さらに多くのプロトタイプ関数を作成しました。ちょうどケーキのように簡単だと思っていたとき...。
それはそれほど簡単ではありません
最初に注意を引いたのは、式処理サブルーチンからです。
/*
** 式を解析し、コードを生成します。
*/
unsigned char *expresion() {
struct nodo *origen;
unsigned char *tipo;
origen = ultimo_nodo;
tipo = almacena_expresion(SI);
evalua_arbol(NO);
libera_arbol(ultimo_nodo);
ultimo_nodo = origen;
return tipo;
}
この関数はC式のコンパイルを行い、型へのポインタを返します。しかし、almacena_expresion関数は次のように行います。
/*
** 式を解析し、メモリに保持します。
*/
int almacena_expresion(operador_coma)
int operador_coma;
{
int info[1], izq;
if (operador_coma) {
if (nivel0(info)) carga_valor(info);
} else {
if (nivel1(info)) carga_valor(info);
}
return info[0];
}
型ポインタがint配列に保存されています。私はccexpr.cファイルを完全に書き直して、int info[1]をunsigned char *info;に変更し始めました。expresion_constanteでは、int origenをstruct nodo *origenに置き換える必要がありました。私のコンパイラはポインタから整数への代入やその逆について警告を出さなかったようです。
すぐに、型処理にも独自のポインタ問題があることがわかりました。例えば、型コピーサブルーチンがあり、それは次のようになります。
/*
** 型を次の利用可能な位置にコピーします。
*/
copia_tipo(tipo)
unsigned char *tipo;
{
int a;
while (*tipo >= APUNTADOR) {
if (*tipo == MATRIZ) {
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
} else
guarda_tipo(*tipo++);
}
if (*tipo == STRUCT) {
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
guarda_tipo(*tipo++);
}
guarda_tipo(*tipo++);
}
このサブルーチンはC型の説明のコピーを行います。これは、int a, b, c;のように複数の変数の型を同じに設定する場合に役立ちます。guarda_tipo関数は単にバイトを型プールに保存します。しかし、配列型(MATRIZ)には配列長(4バイトとして保存された32ビット)が含まれており、構造体型(STRUCT)は構造体定義を指します。32ビットポインタが整数に変換され、4バイトとして保存されています。再び、現代の64ビットアーキテクチャには完全に移植できません。
最も簡単な方法は、8回のguarda_tipo関数呼び出し(8バイトに8バイト)に拡張することです。しかし幸いなことに、別の方法を見つけました。
また、構造体が同じバイトプールに割り当てられているという問題もあります。これは次のようになります(恥ずかしいコードが以下に表示されます)。
/*
** 新しい構造体。
*/
unsigned char *nueva_estructura(nombre)
unsigned char *nombre;
{
unsigned char *ap;
int conteo;
if(ultima_estruct != NULL)
escribe_entero(ultima_estruct + EST_SIG, sig_tipo);
ultima_estruct = sig_tipo;
if(lista_estruct == NULL)
lista_estruct = sig_tipo;
conteo = 0;
while(conteo++ < EST_NOMBRE)
guarda_tipo(0);
while(*nombre)
guarda_tipo(*nombre++);
guarda_tipo(0);
return ultima_estruct;
}
そして、構造体の各メンバーも型プールに割り当てられます。さらに、私はこれを3つのマクログループに追跡しました。各グループは疑似構造体を定義しています。
/*構造体の定義*/
#define EST_QUE_ES 0 /* char, structまたはenumのラベルかどうかを示す */
#define EST_ES_UNION 1 /* char, unionかstructかを示す */
#define EST_TAM 2 /* int, struct/unionの合計サイズ */
#define EST_LISTA 6 /* char*, メンバーリスト */
#define EST_SIG 10 /* char*, 次のラベル */
#define EST_NOMBRE 14 /* char[], ラベル */
/*メンバーの定義*/
#define MIE_TIPO 0 /* char*, メンバーの型 */
#define MIE_POSICION 4 /* int, 構造体内の位置 */
#define MIE_SIG 8 /* char*, 次のメンバー */
#define MIE_NOMBRE 12 /* メンバー名 */
/*列挙子の定義*/
#define ENUM_VALOR 0 /* int, 列挙子の値 */
#define ENUM_SIG 4 /* char*, 次の列挙子 */
#define ENUM_NOMBRE 8 /* char[], 列挙子名 */
ここに5つのポインタが埋め込まれています。これを変換して作業することができました。