OpenWatcom の C コンパイラの _Far16 (_Seg16) ポインタの扱いがバグってるっぽい件について

忘れないうちに書いとこう…。

テストコード : 引数の 32bit ポインタを 16bit FAR ポインタに変換し、16bit API を(サンク経由で)呼び出すって感じのコード(test16.c)

#define INCL_DOS
#define INCL_DOSERRORS
#include <os2.h>

/* declare 16bit far pointer */
#if defined(__GNUC__)
typedef ULONG PTEST16;
#elif defined(__BORLANDC__)
typedef void __far16 * PTEST16;
#else
typedef void * _Seg16 PTEST16;
#endif

#ifdef __cplusplus
extern "C" USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */
#else
extern USHORT APIENTRY16 DOS16TEST(PTEST16 pt16);     /* (not exist in real) */
#endif

USHORT APIENTRY DOS32TEST_1(void *pv)
{
  USHORT rc;
  
  rc = DOS16TEST((PTEST16)pv);
  return rc;
}

USHORT APIENTRY DOS32TEST_2(void *pv)
{
  USHORT rc;
  PTEST16 pt16;
  
  pt16 = (PTEST16)pv;
  rc = DOS16TEST(pt16);
  return rc;
}

これを OpenWatcom の C コンパイラ(wcc386)でコンパイルし、obj を逆アセンブルして asm を得る。

wcc386 -q -s -bt=os2 -bd -d1 test16.c && wdis -a -s=test16.c test16.obj > test16_wcc.asm

んで結果(test16_wcc.asm):

.387
.386p
.model flat
		PUBLIC	DOS32TEST_1
		PUBLIC	DOS32TEST_2
		EXTRN	__TNK:BYTE
		EXTRN	DOS16TEST:BYTE
		EXTRN	__Far16Func2:BYTE
		EXTRN	__FlatToFar16:BYTE
		EXTRN	__DLLstart_:BYTE
DGROUP		GROUP	CONST,CONST2,_DATA
_TEXT		SEGMENT	BYTE PUBLIC USE32 'CODE'
		ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP

; #define INCL_DOS
; #define INCL_DOSERRORS
; #include <os2.h>
; 
; /* declare 16bit far pointer */
; #if defined(__GNUC__)
; typedef ULONG PTEST16;
; #elif defined(__BORLANDC__)
; typedef void __far16 * PTEST16;
; #else
; typedef void * _Seg16 PTEST16;
; #endif
; 
; extern USHORT APIENTRY16 DOS16TEST(PTEST16 pt16);  /* (not exist in real) */
; 
; USHORT APIENTRY DOS32TEST_1(void *pv)
DOS32TEST_1:
    push        esp 
    push        4 
    push        10H 
    call        near ptr FLAT:__TNK        ; prepare for thunk
    push        esi 
    sub         esp,4 

; {
;   USHORT rc;
;   
;   rc = DOS16TEST((PTEST16)pv);
    mov         esi,dword ptr 10H[esp]     ; Oops, casting by PTEST16 has
    mov         dword ptr [esp],esi        ; no effect on pointer...
    mov         ecx,4 
    mov         esi,esp 
    mov         eax,DOS16TEST 
    call        near ptr FLAT:__Far16Func2 ; invoke thunk code

;   return rc;
; }
    add         esp,4 
    pop         esi 
    pop         esp 
    ret         

; 
; USHORT APIENTRY DOS32TEST_2(void *pv)
DOS32TEST_2:
    push        esp 
    push        4 
    push        10H 
    call        near ptr FLAT:__TNK 
    push        esi 
    sub         esp,4 

; {
;   USHORT rc;
;   PTEST16 pt16;
;   
;   pt16 = (PTEST16)pv;
    mov         eax,dword ptr 10H[esp] 
    call        near ptr FLAT:__FlatToFar16 ; convert the flat pointer to _Far16

;   rc = DOS16TEST(pt16);
    call        near ptr FLAT:__FlatToFar16 ; what!? why convert again?
    mov         dword ptr [esp],eax 
    mov         ecx,4 
    mov         esi,esp 
    mov         eax,DOS16TEST 
    call        near ptr FLAT:__Far16Func2 

;   return rc;
; }
    add         esp,4 
    pop         esi 
    pop         esp 
    ret         
_TEXT		ENDS
CONST		SEGMENT	DWORD PUBLIC USE32 'DATA'
CONST		ENDS
CONST2		SEGMENT	DWORD PUBLIC USE32 'DATA'
CONST2		ENDS
_DATA		SEGMENT	DWORD PUBLIC USE32 'DATA'
_DATA		ENDS
		END

えーと、つまり、

  • 関数の引数上で直接キャストしたときは、ポインタが変換されずにそのまま渡されてる(つまり PTEST16 のキャストがまったく効いてない)
  • いったん PTEST16 な変数を受け皿にしたほうは、何故か2度も変換ルーチンが呼び出されている。なんでやねん。

んで、今度は C++ コンパイラ(wpp386)でコンパイルしたときの結果(test16_wpp.asm):

.387
.386p
.model flat
		PUBLIC	`W?DOS32TEST_1$n(pnv)us`
		PUBLIC	`W?DOS32TEST_2$n(pnv)us`
		EXTRN	__TNK:BYTE
		EXTRN	__FlatToFar16:BYTE
		EXTRN	DOS16TEST:BYTE
		EXTRN	__Far16Func2:BYTE
		EXTRN	__DLLstart_:BYTE
DGROUP		GROUP	CONST,CONST2,_DATA,_BSS
_TEXT		SEGMENT	BYTE PUBLIC USE32 'CODE'
		ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP

; #define INCL_DOS
; #define INCL_DOSERRORS
; #include <os2.h>
; 
; /* declare 16bit far pointer */
; #if defined(__GNUC__)
; typedef ULONG PTEST16;
; #elif defined(__BORLANDC__)
; typedef void __far16 * PTEST16;
; #else
; typedef void * _Seg16 PTEST16;
; #endif
; 
; #ifdef __cplusplus
; extern "C" USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */
; #else
; extern USHORT APIENTRY16 DOS16TEST(PTEST16 pt16);     /* (not exist in real) */
; #endif
; 
; USHORT APIENTRY DOS32TEST_1(void *pv)
`W?DOS32TEST_1$n(pnv)us`:
    push        esp 
    push        4 
    push        10H 
    call        near ptr FLAT:__TNK 
    push        esi 
    sub         esp,4 

; {
;   USHORT rc;
;   
;   rc = DOS16TEST((PTEST16)pv);
    mov         eax,dword ptr 10H[esp] 
    call        near ptr FLAT:__FlatToFar16 
    mov         dword ptr [esp],eax 
    mov         ecx,4 
    mov         esi,esp 
    mov         eax,DOS16TEST 
    call        near ptr FLAT:__Far16Func2 

;   return rc;
    add         esp,4 
    pop         esi 
    pop         esp 
    ret         

; }
; 
; USHORT APIENTRY DOS32TEST_2(void *pv)
`W?DOS32TEST_2$n(pnv)us`:
    push        esp 
    push        4 
    push        10H 
    call        near ptr FLAT:__TNK 
    push        esi 
    sub         esp,4 

; {
;   USHORT rc;
;   PTEST16 pt16;
;   
;   pt16 = (PTEST16)pv;
;   rc = DOS16TEST(pt16);
    mov         eax,dword ptr 10H[esp] 
    call        near ptr FLAT:__FlatToFar16 
    mov         dword ptr [esp],eax 
    mov         ecx,4 
    mov         esi,esp 
    mov         eax,DOS16TEST 
    call        near ptr FLAT:__Far16Func2 

;   return rc;
    add         esp,4 
    pop         esi 
    pop         esp 
    ret         
_TEXT		ENDS
CONST		SEGMENT	BYTE PUBLIC USE32 'DATA'
CONST		ENDS
CONST2		SEGMENT	BYTE PUBLIC USE32 'DATA'
CONST2		ENDS
_DATA		SEGMENT	BYTE PUBLIC USE32 'DATA'
_DATA		ENDS
_BSS		SEGMENT	BYTE PUBLIC USE32 'BSS'
_BSS		ENDS

		END

今度はどっちの関数でも __FlatToFar16 が一度だけ呼び出されているのでした。

C のほうだけバグってるってのも何か珍しいなあ。型情報のぬるさが仇になっているのでしょうか。
報告しないと直してもらえないよなあこれ。しかし英語でどうやって……