Files
five/docs/harbour-type-system-analysis.md
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

34 KiB

Harbour Type System Analysis & Go Porting Strategy

Harbour 핵심 모듈(compiler, vm, rtl, macro, pp, rdd)의 Go 포팅을 위한 변수 타입 시스템 면밀 분석 문서

Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) All rights reserved.


목차

  1. HB_ITEM 물리적 구조
  2. 타입 플래그 맵
  3. 타입별 상세 분석
  4. GC 통합 구조
  5. 산술 연산 동작
  6. 비교 연산 동작
  7. Go Value 설계: NaN-boxing 검증 및 기각
  8. Go Value 설계: Tagged Value 16B (채택안)
  9. 타입별 Go 매핑 상세
  10. 소스 참조

1. HB_ITEM 물리적 구조

정의 위치

  • include/hbapi.h:393-415

구조

typedef struct _HB_ITEM
{
   HB_TYPE type;              // HB_U32 (4 bytes)
   union
   {
      struct hb_struArray     asArray;
      struct hb_struBlock     asBlock;
      struct hb_struDateTime  asDateTime;
      struct hb_struDouble    asDouble;
      struct hb_struInteger   asInteger;
      struct hb_struLogical   asLogical;
      struct hb_struLong      asLong;
      struct hb_struPointer   asPointer;
      struct hb_struHash      asHash;
      struct hb_struMemvar    asMemvar;
      struct hb_struRefer     asRefer;
      struct hb_struEnum      asEnum;
      struct hb_struExtRef    asExtRef;
      struct hb_struString    asString;
      struct hb_struSymbol    asSymbol;
      struct hb_struRecover   asRecover;
   } item;
} HB_ITEM, * PHB_ITEM;

크기 분석 (64비트 기준)

멤버 필드 구성 바이트
asDouble double(8) + ushort(2) + ushort(2) 12
asLong HB_MAXINT=int64(8) + ushort(2) 10
asInteger int(4) + ushort(2) 6
asLogical HB_BOOL=int(4) 4
asDateTime long(8) + long(8) 16
asString HB_SIZE(8) + HB_SIZE(8) + char*(8) 24
asArray ptr(8) 8
asHash ptr(8) 8
asBlock ptr(8) + ushort(2)x4 16
asPointer ptr(8) + bool(4) + bool(4) 16
asRefer union ptr(8) + HB_ISIZ(8) + HB_ISIZ(8) 24
asSymbol ptr(8) + ptr(8) + ushort(2) + ushort(2) 20
asRecover ptr(8) + HB_SIZE(8) + ushort(2) + ushort(2) 20
asEnum ptr(8) + ptr(8) + HB_ISIZ(8) 24
asExtRef ptr(8) + ptr(8) 16
asMemvar ptr(8) 8

Union 최대 크기 = 24 bytes (asString, asRefer, asEnum)

sizeof(HB_ITEM) = 4(type) + 4(padding) + 24(union) = 32 bytes

스택에 HB_ITEM이 1000개 쌓이면 32KB. 캐시 라인(64B)당 HB_ITEM 2개.


2. 타입 플래그 맵

정의 위치

  • include/hbapi.h:69-99
  • HB_TYPE = HB_U32 (4바이트 부호 없는 정수, include/hbdefs.h:581)

기본 타입 플래그

비트       플래그             용도                   GC 대상   스칼라/복합
───────────────────────────────────────────────────────────────────────
0x00000    HB_IT_NIL          빈 값                   -        스칼라
0x00001    HB_IT_POINTER      C 포인터                GC       복합
0x00002    HB_IT_INTEGER      int (32비트)             -        스칼라
0x00004    HB_IT_HASH         해시 테이블              GC       복합
0x00008    HB_IT_LONG         int64 (HB_MAXINT)        -        스칼라
0x00010    HB_IT_DOUBLE       double + 정밀도 메타     -        스칼라
0x00020    HB_IT_DATE         율리우스 일자            -        스칼라
0x00040    HB_IT_TIMESTAMP    일자 + 밀리초            -        스칼라
0x00080    HB_IT_LOGICAL      불리언                   -        스칼라
0x00100    HB_IT_SYMBOL       심볼/함수 참조           -        스칼라
0x00200    HB_IT_ALIAS        워크에어리어 별칭        -        내부용
0x00400    HB_IT_STRING       문자열                   RefCnt   복합
0x00800    HB_IT_MEMOFLAG     메모 플래그 수식자       -        수식자
0x01000    HB_IT_BLOCK        코드 블록                GC       복합
0x02000    HB_IT_BYREF        참조 (by-reference)      GC       복합
0x04000    HB_IT_MEMVAR       메모리 변수              -        내부용
0x08000    HB_IT_ARRAY        배열/객체                GC       복합
0x10000    HB_IT_ENUM         열거 (FOR EACH)          -        내부용
0x20000    HB_IT_EXTREF       외부 참조                -        내부용
0x40000    HB_IT_DEFAULT      기본값 플래그            -        수식자
0x80000    HB_IT_RECOVER      예외 복구                -        내부용

복합 타입 그룹

HB_IT_OBJECT   = HB_IT_ARRAY                                // 객체 = 배열 (uiClass로 구분)
HB_IT_MEMO     = HB_IT_MEMOFLAG | HB_IT_STRING              // 메모 문자열
HB_IT_NUMERIC  = HB_IT_INTEGER | HB_IT_LONG | HB_IT_DOUBLE  // 모든 수치
HB_IT_NUMINT   = HB_IT_INTEGER | HB_IT_LONG                 // 정수만
HB_IT_DATETIME = HB_IT_DATE | HB_IT_TIMESTAMP               // 모든 날짜
HB_IT_COMPLEX  = BLOCK | ARRAY | HASH | POINTER | BYREF | STRING  // 해제 필요
HB_IT_GCITEM   = BLOCK | ARRAY | HASH | POINTER | BYREF           // GC 추적 대상
HB_IT_EVALITEM = BLOCK | SYMBOL                                    // 실행 가능
HB_IT_HASHKEY  = INTEGER | LONG | DOUBLE | DATE | TIMESTAMP | STRING | POINTER

타입 분류 요약

사용자 노출 타입 (10종):
  NIL, LOGICAL, INTEGER, LONG, DOUBLE, DATE, TIMESTAMP, STRING, ARRAY(OBJECT), HASH, BLOCK

VM 내부 전용 타입 (7종):
  POINTER, SYMBOL, BYREF, MEMVAR, ENUM, EXTREF, RECOVER

수식자 (3종):
  MEMOFLAG, ALIAS, DEFAULT

3. 타입별 상세 분석

3.1 수치 타입: INTEGER / LONG / DOUBLE

구조 정의

// include/hbapi.h:322-326
struct hb_struInteger {
    int value;            // 32-bit signed: -2,147,483,648 ~ 2,147,483,647
    HB_USHORT length;     // 표시 폭 (STR() 등에서 사용)
};

// include/hbapi.h:328-332
struct hb_struLong {
    HB_MAXINT value;      // 64-bit signed (HB_LONGLONG): -9.2x10^18 ~ 9.2x10^18
    HB_USHORT length;     // 표시 폭
};

// include/hbapi.h:315-320
struct hb_struDouble {
    double value;          // 64-bit IEEE 754
    HB_USHORT length;      // 전체 표시 폭
    HB_USHORT decimal;     // 소수점 이하 자릿수
};

HB_MAXINT 정의 (include/hbdefs.h:435-466)

// 64비트 플랫폼 (일반적):
//   ULONG_MAX == UINT_MAX인 경우 → HB_MAXINT = HB_LONGLONG (int64)
//   그 외 → HB_MAXINT = long
//
// HB_VMINT_MAX  = INT_MAX  (2,147,483,647)    ← INTEGER 범위
// HB_VMLONG_MAX = LONGLONG_MAX (9.2x10^18)    ← LONG 범위

length/decimal 메타데이터의 역할

lengthdecimal연산에 영향을 주지 않고 표시(formatting)에만 사용된다:

  • STR(nValue)length 폭으로 포맷
  • STR(nValue, nWidth, nDec) → 명시적 지정
  • TRANSFORM(), @...SAYlength/decimal 참조
  • HB_DEFAULT_WIDTH(255), HB_DEFAULT_DECIMALS(255) = 미지정 상태

자동 타입 승격 규칙

값 할당 시 (HB_ITEM_PUT_NUMINTRAW 매크로):
  HB_LIM_INT(v) → INTEGER (값이 int 범위 내)
  else          → LONG

산술 오버플로우 시:
  INTEGER + INTEGER → 결과가 오버플로우 → DOUBLE
  LONG + LONG       → 결과가 오버플로우 → DOUBLE

오버플로우 감지 로직 (덧셈):
  if (b >= 0) { overflow = (result < a); }
  else        { overflow = (result >= a); }

산술 결과 소수점 규칙

연산 결과 decimal
+, - max(dec1, dec2)
* dec1 + dec2
/ 항상 DOUBLE 반환
% 항상 DOUBLE 반환
** 항상 DOUBLE 반환

3.2 문자열: STRING

구조 정의

// include/hbapi.h:369-374
struct hb_struString {
    HB_SIZE length;       // 문자열 길이 (바이트 수, null 미포함)
    HB_SIZE allocated;    // 할당된 버퍼 크기 (0 = 정적/상수)
    char * value;         // 문자열 데이터 포인터
};

3가지 문자열 모드

모드 1: 정적 문자열 (allocated == 0)

- 컴파일 타임 상수 문자열
- 0~1 바이트 문자열 → hb_szAscii[] 전역 테이블 참조 (256개 사전 할당)
- GC/free 대상 아님
- hb_itemPutCConst(), hb_itemPutCLConst()로 생성

모드 2: 동적 문자열 (allocated > 0, refcount == 1)

- hb_xgrab()으로 할당
- 단일 소유자, 직접 수정(in-place mutation) 가능
- hb_itemPutC(), hb_itemPutCL()로 생성

모드 3: 공유 문자열 (allocated > 0, refcount > 1)

- hb_xRefInc()로 참조 카운트 증가
- 수정 시 COW (Copy-On-Write): hb_itemUnShareString() 호출
- hb_itemCopy()로 복사 시 refcount++ (데이터 복사 없음)

메모리 레이아웃

  hb_xgrab() 할당 블록:
  ┌──────────────┬─────────────────────────┐
  │ HB_COUNTER   │  string data            │
  │ (refcount)   │  (length + 1 bytes)     │
  │ = size_t     │  null-terminated        │
  └──────────────┴─────────────────────────┘
                  ↑
                  value 포인터가 여기를 가리킴
                  HB_COUNTER_PTR(value)로 refcount 접근

참조 카운트 API (include/hbapi.h:520-527)

#define hb_xRefInc( p )    (++(*HB_COUNTER_PTR( p )))
#define hb_xRefDec( p )    (--(*HB_COUNTER_PTR( p )) == 0)
#define hb_xRefFree( p )   do { if( hb_xRefDec(p) ) hb_xfree(HB_MEM_PTR(p)); } while(0)
#define hb_xRefCount( p )  (*HB_COUNTER_PTR( p ))

MEMO 플래그

HB_IT_MEMO = HB_IT_STRING | HB_IT_MEMOFLAG (0x00C00)

- DBF 메모 필드에서 읽어온 문자열에 설정
- 일부 비교 동작이 달라질 수 있음
- 연결(concatenation) 후 MEMOFLAG 제거됨

핵심 발견: STRING은 Mark-Sweep GC가 아닌 참조 카운트로 관리됨. 나머지 복합 타입(ARRAY, HASH, BLOCK, POINTER)만 GC 대상.


3.3 날짜/타임스탬프: DATE / TIMESTAMP

구조 정의

// include/hbapi.h:309-313
struct hb_struDateTime {
    long julian;     // 율리우스 일 번호 (기원전 4713년 1월 1일 기준)
    long time;       // 밀리초 (DATE면 0, TIMESTAMP면 0~86,399,999)
};

두 타입이 같은 구조체를 공유

구분 type 플래그 julian time
DATE 0x00020 율리우스 일 항상 0
TIMESTAMP 0x00040 율리우스 일 0~86,399,999 (밀리초)

산술 동작

DATE + INTEGER       → DATE  (일수 더하기)
DATE - DATE          → LONG  (일수 차이)
DATE + DATE          → DATE  (줄리안 합산 — Clipper 호환 quirk)

TIMESTAMP + DOUBLE   → TIMESTAMP  (소수점 = 일의 분수, hb_vmTimeStampAdd)
TIMESTAMP - TIMESTAMP:
  time 차이 있으면 → DOUBLE  (HB_TIMEDIFF_DEC 소수점)
  time 차이 없으면 → LONG    (일수만)

DATE + TIMESTAMP     → 에러 (혼합 불가)

비교 동작

DATE == DATE           → julian 비교
TIMESTAMP == TIMESTAMP → julian AND time 모두 일치해야
DATE == TIMESTAMP      → 에러 (타입 불일치)

DATE < DATE            → julian < julian
TIMESTAMP < TIMESTAMP  → julian < julian, 같으면 time < time

3.4 배열/객체: ARRAY

구조 정의

// include/hbapi.h:283-286 (HB_ITEM 내)
struct hb_struArray {
    struct _HB_BASEARRAY * value;    // GC 관리 포인터
};

// include/hbapi.h:418-425
typedef struct _HB_BASEARRAY {
    PHB_ITEM    pItems;       // HB_ITEM 배열 (각 32바이트)
    HB_SIZE     nLen;         // 현재 요소 수
    HB_SIZE     nAllocated;   // 할당된 슬롯 수
    HB_USHORT   uiClass;      // 0이면 배열, >0이면 객체 (클래스 인덱스)
    HB_USHORT   uiPrevCls;    // super 접근 복원용
} HB_BASEARRAY;

메모리 레이아웃

  ┌───────────────────┐     ┌─────────────────────────────────────────┐
  │ HB_GARBAGE header │     │  HB_ITEM[0]  HB_ITEM[1]  HB_ITEM[2]   │
  │ (16 bytes)        │     │  32 bytes    32 bytes    32 bytes      │
  ├───────────────────┤     └─────────────────────────────────────────┘
  │ HB_BASEARRAY      │            ↑
  │  pItems ──────────┼────────────┘
  │  nLen = 3         │
  │  nAllocated = 4   │
  │  uiClass = 0      │   ← 0: 배열
  │  uiPrevCls = 0    │
  └───────────────────┘

객체 = 배열 + 클래스

HB_IT_OBJECT == HB_IT_ARRAY   (같은 타입 플래그)
구분: uiClass > 0 이면 객체

객체의 pItems = 프로퍼티(필드) 배열
클래스 메서드는 별도 클래스 레지스트리에서 조회
연산자 오버로딩: HB_OO_OP_PLUS 등으로 산술/비교 재정의 가능

배열 성장/수축 전략

성장: new_alloc = (old_alloc / 2) + 1 + needed   → 약 1.5배 증가
수축: nLen < (nAllocated / 2) 일 때 재할당

3.5 해시 테이블: HASH

구조 정의

// src/vm/hashes.c:63-77 (내부 구조, _HB_HASH_INTERNAL_ 정의 시)
typedef struct _HB_HASHPAIR {
    HB_ITEM key;            // 키: 32 bytes
    HB_ITEM value;          // 값: 32 bytes
} HB_HASHPAIR;              // 쌍당 64 bytes

typedef struct _HB_BASEHASH {
    PHB_HASHPAIR pPairs;     // key-value 쌍 배열
    PHB_ITEM     pDefault;   // 기본값 (auto-add 모드)
    HB_SIZE *    pnPos;      // 삽입 순서 인덱스 (HB_HASH_KEEPORDER)
    HB_SIZE      nSize;      // 할당된 쌍 수
    HB_SIZE      nLen;       // 사용 중인 쌍 수
    int          iFlags;     // 동작 플래그
} HB_BASEHASH;

검색 방식

정렬된 배열 + 이진 검색 (해시 함수 기반이 아님!)
→ O(log N) 검색
→ 삽입 시 정렬 유지 비용

유효한 키 타입: INTEGER, LONG, DOUBLE, DATE, TIMESTAMP, STRING, POINTER
다른 타입 키 시도 → 에러

초기 할당

#define HB_HASH_ITEM_ALLOC  16  // 초기 16쌍 할당

3.6 코드 블록: BLOCK

구조 정의

// include/hbapi.h:293-300 (HB_ITEM 내)
struct hb_struBlock {
    struct _HB_CODEBLOCK * value;   // GC 관리 포인터
    HB_USHORT paramcnt;              // 전달된 파라미터 수
    HB_USHORT lineno;                // 소스 라인 번호
    HB_USHORT hclass;                // 메서드 내 생성 시 클래스 인덱스
    HB_USHORT method;                // 메서드 번호
};

// include/hbapi.h:436-445
typedef struct _HB_CODEBLOCK {
    const HB_BYTE * pCode;      // 바이트코드 포인터
    PHB_SYMB    pSymbols;        // 심볼 테이블 참조
    PHB_SYMB    pDefSymb;        // 정의된 위치의 심볼
    PHB_ITEM    pLocals;         // 캡처된 로컬 변수 테이블
    void *      pStatics;        // STATIC 프레임 베이스
    HB_USHORT   uiLocals;        // 캡처된 로컬 변수 수
    HB_SHORT    dynBuffer;       // 동적 버퍼 할당 여부
} HB_CODEBLOCK;

디태치된(Detached) 로컬

코드 블록이 정의된 함수를 벗어나도 캡처된 로컬이 살아남음:
- pLocals 배열에 로컬 변수의 복사본이 저장됨
- Go의 클로저와 유사하나, 명시적 관리 필요
- 참조로 캡처: 블록 내에서 로컬 수정 시 원본에 반영

3.7 참조: BYREF

구조 정의

// include/hbapi.h:344-354
struct hb_struRefer {
    union {
        struct _HB_BASEARRAY * array;       // 스태틱/배열 아이템 참조
        struct _HB_CODEBLOCK * block;       // 코드블록 로컬 참조
        struct _HB_ITEM * itemPtr;          // 아이템 직접 참조
        struct _HB_ITEM ** *itemsbasePtr;   // 로컬 변수 참조
    } BasePtr;
    HB_ISIZ offset;    // 0 = 스태틱, >0 = 스택 오프셋
    HB_ISIZ value;     // 대상 인덱스/오프셋
};

4가지 참조 대상

대상 BasePtr 사용 offset value 설명
로컬 변수 itemsbasePtr 스택 베이스 오프셋 로컬 인덱스 hb_stackItemBasePtr()
스태틱 변수 array 0 (고정) 스태틱 인덱스 HB_BASEARRAY의 pItems
코드블록 로컬 block - 로컬 인덱스 HB_CODEBLOCK의 pLocals
기타 아이템 itemPtr - - 직접 포인터

참조 해제 API (include/hbapiitm.h:169-172)

hb_itemUnRef()       // 완전 역참조 (체인 끝까지 따라감)
hb_itemUnRefOnce()   // 1단계 역참조
hb_itemUnRefRefer()  // 마지막 참조만 남김
hb_itemUnRefWrite()  // 쓰기용 역참조 (COW 유사)

3.8 심볼: SYMBOL

구조 정의

// include/hbapi.h:376-382
struct hb_struSymbol {
    PHB_SYMB        value;           // HB_SYMB 포인터
    PHB_STACK_STATE stackstate;      // 함수 호출 시 스택 상태
    HB_USHORT       paramcnt;        // 전달된 파라미터 수
    HB_USHORT       paramdeclcnt;    // 선언된 파라미터 수
};

// include/hbvmpub.h:199-214
typedef struct _HB_SYMB {
    const char *   szName;           // 심볼 이름
    union {
        HB_SYMBOLSCOPE value;        // 스코프 플래그
        void *         pointer;      // 정렬 맞춤용
    } scope;
    union {
        PHB_FUNC       pFunPtr;      // 네이티브 함수 포인터
        PHB_PCODEFUNC  pCodeFunc;    // pcode 함수 (HRB)
        void *         pStaticsBase; // 스태틱 배열 베이스
    } value;
    PHB_DYNS       pDynSym;          // 동적 심볼 포인터
} HB_SYMB;

용도

- 스택에 SYMBOL이 push되면 함수 호출의 "프레임 마커" 역할
- HB_FS_PCODEFUNC 플래그: pcode 함수 (HRB 동적 로딩)
- HB_FS_DEFERRED 플래그: 지연 바인딩 (pDynSym 경유)
- stackstate: 이전 함수의 스택 상태를 보존 (중첩 호출 시)

3.9 포인터: POINTER

구조 정의

// include/hbapi.h:302-307
struct hb_struPointer {
    void * value;        // C 포인터
    HB_BOOL collect;     // GC가 수거해야 하는지
    HB_BOOL single;      // 단일 소유자인지
};

용도

- C 확장과의 인터페이스용
- collect == TRUE: GC가 HB_GC_FUNCS의 release 함수를 호출하여 정리
- collect == FALSE: 외부에서 관리되는 포인터 (GC 무시)
- single == TRUE: 복사 시 참조가 아닌 이동

3.10 내부 전용 타입

MEMVAR (include/hbapi.h:339-342)

struct hb_struMemvar {
    struct _HB_ITEM * value;    // PUBLIC/PRIVATE 변수의 실제 값을 가리킴
};

MEMVAR + BYREF 조합으로 사용. 동적 심볼 테이블의 memvar 슬롯 참조.

ENUM (include/hbapi.h:356-361)

struct hb_struEnum {
    struct _HB_ITEM * basePtr;    // 반복 대상 (배열/해시/문자열)
    struct _HB_ITEM * valuePtr;   // 현재 값
    HB_ISIZ offset;               // 현재 인덱스
};

FOR EACH 구문의 반복자. VM 내부에서만 사용.

EXTREF (include/hbapi.h:363-367)

struct hb_struExtRef {
    void * value;                       // 외부 참조 값
    const struct _HB_EXTREF * func;     // read/write/copy/clear/mark 함수 테이블
};

확장 참조. 외부 시스템과의 연동용 가상 함수 테이블.

RECOVER (include/hbapi.h:384-390)

struct hb_struRecover {
    const HB_BYTE * recover;    // RECOVER 코드 주소
    HB_SIZE         base;       // 이전 recover 베이스
    HB_USHORT       flags;      // 이전 복구 상태
    HB_USHORT       request;    // 요청된 동작 (QUIT, BREAK 등)
};

BEGIN SEQUENCE ... RECOVER ... END 구문의 예외 처리 엔벨로프.


4. GC 통합 구조

GC 헤더 (src/vm/garbage.c:95-102)

typedef struct HB_GARBAGE_ {
    struct HB_GARBAGE_ * pNext;    // 다음 블록 (이중 연결 리스트)
    struct HB_GARBAGE_ * pPrev;    // 이전 블록
    const HB_GC_FUNCS *  pFuncs;   // mark/release 함수 테이블
    HB_USHORT locked;              // 잠금 카운터
    HB_USHORT used;                // 사용/미사용 마크
} HB_GARBAGE;

GC 관리 메모리 레이아웃

  ┌────────────────────────┐
  │  HB_GARBAGE header     │ ~28 bytes (+ padding)
  │  ├ pNext     (8)       │ 이중 연결 리스트
  │  ├ pPrev     (8)       │
  │  ├ pFuncs    (8)       │ mark/release 콜백
  │  ├ locked    (2)       │ GC 잠금 카운터
  │  └ used      (2)       │ mark-sweep 플래그
  ├────────────────────────┤
  │  실제 데이터            │ HB_BASEARRAY, HB_BASEHASH,
  │  (가변 크기)            │ HB_CODEBLOCK 등
  └────────────────────────┘

타입별 메모리 관리 방식

타입 관리 방식 해제 트리거
ARRAY Mark-Sweep GC hb_gcAllocRaw() + GC 사이클
HASH Mark-Sweep GC hb_gcAllocRaw() + GC 사이클
BLOCK Mark-Sweep GC hb_gcAllocRaw() + GC 사이클
POINTER (collect) Mark-Sweep GC hb_gcAllocRaw() + GC 사이클
STRING (dynamic) 참조 카운트 hb_xRefFree() (refcount==0)
STRING (static) 관리 안 함 프로그램 종료 시
스칼라 타입 관리 안 함 HB_ITEM 소멸 시

중요: STRING과 나머지 복합 타입의 메모리 관리 방식이 다르다. STRING = 참조 카운트 (COW), ARRAY/HASH/BLOCK = GC (mark-sweep).


5. 산술 연산 동작

소스 위치

  • src/vm/hvm.c:3285-3770 (hb_vmPlus, hb_vmMinus, hb_vmMult, hb_vmDivide, hb_vmModulus, hb_vmPower)

덧셈 (+) 타입 매트릭스

좌항 \ 우항 NUMINT DOUBLE STRING DATE TIMESTAMP
NUMINT NUMINT(오버플로우→DOUBLE) DOUBLE - - -
DOUBLE DOUBLE DOUBLE - - -
STRING - - STRING(concat) - -
DATE DATE - - DATE(quirk) -
TIMESTAMP TIMESTAMP TIMESTAMP - - TIMESTAMP

뺄셈 (-) 특이사항

STRING - STRING:
  1. 첫 문자열의 후행 공백 제거
  2. 두 문자열 연결
  3. 결과를 첫 문자열 원래 길이로 패딩
  → Clipper 호환 quirk

DATE - DATE → LONG (일수 차이)
TIMESTAMP - TIMESTAMP → time 차이 있으면 DOUBLE, 없으면 LONG

나눗셈 (/, %)

모든 나눗셈: 항상 DOUBLE 반환 (정수 나눗셈 없음)
제수 == 0: EG_ZERODIV 에러 발생

오버플로우 감지 패턴

// 덧셈 (hvm.c:3298)
nResult = nNumber1 + nNumber2;
if( nNumber2 >= 0 ? nResult >= nNumber1 : nResult < nNumber1 )
    stays NUMINT;   // 안전
else
    promotes to DOUBLE;  // 오버플로우

// 뺄셈 (hvm.c:3401)
nResult = nNumber1 - nNumber2;
if( nNumber2 <= 0 ? nResult >= nNumber1 : nResult < nNumber1 )
    stays NUMINT;
else
    promotes to DOUBLE;

6. 비교 연산 동작

소스 위치

  • src/vm/hvm.c:3880-4450 (hb_vmEqual, hb_vmExactlyEqual, hb_vmNotEqual, hb_vmLess, etc.)

동등 비교 (=, ==)

좌항 \ 우항 NIL NUMINT DOUBLE STRING DATE TIMESTAMP LOGICAL ARRAY HASH BLOCK POINTER
NIL TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
NUMINT FALSE 정수== double변환 - - - - - - - -
STRING FALSE - - strcmp - - - - - - -
LOGICAL FALSE - - - - - XOR규칙 - - - -
ARRAY FALSE - - - - - - ptr== - - -

Logical 비교 특이사항

// Clipper quirk: XOR 같은 동작
result = (pItem1->value ? pItem2->value : !pItem2->value);
// TRUE = TRUE  → TRUE
// TRUE = FALSE → FALSE
// FALSE = TRUE → FALSE (not FALSE!)
// FALSE = FALSE → TRUE

정렬 비교 (<, <=, >, >=)

지원 타입: STRING, NUMERIC, DATETIME 만
NIL 비교: 에러 (EG_ARG)
타입 불일치: 에러 (EG_ARG)
객체: 연산자 오버로딩으로 위임 가능

문자열 비교

기본: 대소문자 구분 (case-sensitive)
SET EXACT ON: 전체 길이 비교
SET EXACT OFF: 우항 길이까지만 비교 (Clipper 호환)

7. Go Value 설계: NaN-boxing 검증 및 기각

NaN-boxing 원리

IEEE 754 double에서 NaN 영역 (0x7FF8~이상)을 타입 태그로 활용:
- 48비트 payload에 정수 또는 포인터를 저장
- double 값은 비트 그대로 저장 (NaN이 아닌 모든 값)

타입별 적합성 검증

Harbour 타입 NaN-boxing 가능? 문제점
NIL O 태그 1개 -
LOGICAL O 태그 2개 -
INTEGER O 48비트 충분 -
LONG X 48비트 = +-1.4x10^14, LONG은 +-9.2x10^18 (15.2% 초과)
DOUBLE O 비트 그대로 -
DATE O julian 32비트 충분 -
TIMESTAMP X julian(32) + time(32) = 64비트 전체 필요
STRING X length(8) + allocated(8) + ptr(8) = 24바이트
ARRAY △ 포인터만 GC 연동 필요
HASH △ 포인터만 GC 연동 필요
BLOCK X paramcnt/lineno/hclass/method 메타데이터 손실
SYMBOL X stackstate 포인터 + 카운트 필요
BYREF X 24바이트 union 구조
POINTER X collect/single 플래그 손실

기각 사유

1. LONG의 64비트 전체 범위가 필요 (RecNo, 대용량 테이블, 금액 계산)
2. TIMESTAMP는 julian + time 두 필드 모두 필요
3. length/decimal 메타데이터가 STR()/TRANSFORM()에 필수
4. STRING의 3가지 모드(정적/동적/공유) 정보를 8바이트에 담을 수 없음
5. BYREF의 4가지 참조 대상 구분이 불가

NaN-boxing이 깔끔한 타입: 5개 (NIL, LOGICAL, INTEGER, DOUBLE, DATE)
문제가 있는 타입: 7개 (LONG, TIMESTAMP, STRING, BLOCK, SYMBOL, BYREF, POINTER)
→ 과반수 이상이 부적합하므로 기각

8. Go Value 설계: Tagged Value 16B (채택안)

구조

type Value struct {
    data uint64   // 스칼라 값 또는 포인터
    info uint64   // [type:8][meta:24][aux:32]
}

인코딩 규칙

info 레이아웃:
  비트 63-56: type     (8비트, 최대 256 타입)
  비트 55-32: meta     (24비트, length/decimal/paramcnt 등)
  비트 31-0:  aux      (32비트, 보조값)

타입별 인코딩

타입 data info [type:8] [meta:24] [aux:32]
NIL 0 [tNil] [0] [0]
LOGICAL 0 또는 1 [tLogical] [0] [0]
INTEGER int64(value) [tInt] [length:16, 0:8] [0]
LONG int64(value) [tLong] [length:16, 0:8] [0]
DOUBLE Float64bits(v) [tDouble] [length:16, decimal:8] [0]
DATE int64(julian) [tDate] [0] [0]
TIMESTAMP int64(julian) [tTimestamp] [0] [time:32]
STRING *HbString ptr [tString] [flags:8, 0:16] [length:32]
ARRAY *HbArray ptr [tArray] [class:16, 0:8] [0]
HASH *HbHash ptr [tHash] [flags:8, 0:16] [len:32]
BLOCK *HbBlock ptr [tBlock] [paramcnt:16, 0:8] [lineno:16, hclass:16]
BYREF *refTarget ptr [tByref] [refKind:8, 0:16] [offset:32]
SYMBOL *HbSymbol ptr [tSymbol] [paramcnt:16, declcnt:8] [0]
POINTER unsafe.Pointer [tPointer] [flags:8, 0:16] [0]

Harbour 32B vs Go 16B 크기 비교

타입 Harbour (pad to 32) Go Tagged Value 절감
INTEGER 10 → 32 16 50%
LONG 10 → 32 16 50%
DOUBLE 12 → 32 16 50%
TIMESTAMP 20 → 32 16 50%
STRING 28 → 32 16 (*) 50%
ARRAY 12 → 32 16 50%
BYREF 28 → 32 16 (**) 50%
(*) STRING: length는 info.aux에, allocated는 HbString 구조체 내부에
(**) BYREF: value는 info.aux에, BasePtr union은 data에 포인터로

대안 비교표

방식 크기 스칼라 heap GC pressure 메타데이터 unsafe
HB_ITEM (C) 32B 없음 없음 내장 N/A
NaN-boxing 8B 없음 없음 없음 (손실) 필요
Tagged Value 16B 16B 없음 없음 info에 내장 불필요
interface{} 16B 있음 높음 별도 구조체 불필요

9. 타입별 Go 매핑 상세

9.1 Go 측 보조 구조체

// STRING: 참조 카운트 관리 (Harbour COW 호환)
type HbString struct {
    data     []byte    // Go slice (포인터 + 길이 + 캡)
    refCount int32     // COW용 참조 카운트
    static   bool      // 정적 문자열 여부 (Harbour allocated==0 대응)
}

// ARRAY/OBJECT
type HbArray struct {
    items    []Value   // 16바이트 x N
    class    uint16    // 0이면 배열, >0이면 객체
    prevCls  uint16    // super 접근용
}

// HASH
type HbHash struct {
    pairs    []HbHashPair   // 정렬된 쌍 배열
    positions []int          // 삽입 순서 (KEEPORDER)
    defValue *Value          // auto-add 기본값
    flags    int32
}

type HbHashPair struct {
    key   Value    // 16 bytes
    value Value    // 16 bytes
}

// BLOCK
type HbBlock struct {
    code    []byte       // 바이트코드
    symbols []Symbol     // 심볼 테이블
    defSym  *Symbol      // 정의 위치
    locals  []Value      // 캡처된 로컬 (detached)
    statics *[]Value     // STATIC 프레임
}

// BYREF 대상 구분
const (
    refLocal   byte = iota  // 로컬 변수 참조
    refStatic               // 스태틱 변수 참조
    refBlock                // 코드블록 로컬 참조
    refDirect               // 직접 아이템 참조
)

9.2 Value 생성 헬퍼 (Go)

func makeNil() Value {
    return Value{data: 0, info: uint64(tNil) << 56}
}

func makeBool(b bool) Value {
    var d uint64
    if b { d = 1 }
    return Value{data: d, info: uint64(tLogical) << 56}
}

func makeInt(v int64) Value {
    length := intDisplayLength(v)
    return Value{
        data: uint64(v),
        info: uint64(tInt)<<56 | uint64(length)<<40,
    }
}

func makeLong(v int64) Value {
    length := longDisplayLength(v)
    return Value{
        data: uint64(v),
        info: uint64(tLong)<<56 | uint64(length)<<40,
    }
}

func makeDouble(v float64, length, decimal uint16) Value {
    return Value{
        data: math.Float64bits(v),
        info: uint64(tDouble)<<56 | uint64(length)<<40 | uint64(decimal)<<32,
    }
}

func makeDate(julian int64) Value {
    return Value{
        data: uint64(julian),
        info: uint64(tDate) << 56,
    }
}

func makeTimestamp(julian int64, timeMs int32) Value {
    return Value{
        data: uint64(julian),
        info: uint64(tTimestamp)<<56 | uint64(uint32(timeMs)),
    }
}

9.3 Value 접근 헬퍼 (Go)

func (v Value) Type() byte       { return byte(v.info >> 56) }
func (v Value) IsNil() bool      { return v.Type() == tNil }
func (v Value) IsNumeric() bool  { t := v.Type(); return t == tInt || t == tLong || t == tDouble }
func (v Value) IsNumInt() bool   { t := v.Type(); return t == tInt || t == tLong }
func (v Value) IsString() bool   { return v.Type() == tString }
func (v Value) IsArray() bool    { return v.Type() == tArray }
func (v Value) IsObject() bool   { return v.Type() == tArray && v.arrayClass() > 0 }
func (v Value) IsBlock() bool    { return v.Type() == tBlock }
func (v Value) IsDateTime() bool { t := v.Type(); return t == tDate || t == tTimestamp }

func (v Value) AsInt() int64       { return int64(v.data) }
func (v Value) AsDouble() float64  { return math.Float64frombits(v.data) }
func (v Value) AsBool() bool       { return v.data != 0 }
func (v Value) AsJulian() int64    { return int64(v.data) }
func (v Value) AsTimeMs() int32    { return int32(v.info & 0xFFFFFFFF) }

// 포인터 타입 접근 (unsafe 사용)
func (v Value) asHbString() *HbString { return (*HbString)(unsafe.Pointer(uintptr(v.data))) }
func (v Value) asHbArray() *HbArray   { return (*HbArray)(unsafe.Pointer(uintptr(v.data))) }
func (v Value) asHbHash() *HbHash     { return (*HbHash)(unsafe.Pointer(uintptr(v.data))) }
func (v Value) asHbBlock() *HbBlock   { return (*HbBlock)(unsafe.Pointer(uintptr(v.data))) }

10. 소스 참조

항목 파일 라인
HB_ITEM 정의 include/hbapi.h 393-415
서브 구조체 include/hbapi.h 282-390
HB_BASEARRAY include/hbapi.h 418-425
HB_CODEBLOCK include/hbapi.h 436-445
HB_BASEHASH src/vm/hashes.c 69-77
HB_GARBAGE src/vm/garbage.c 95-102
타입 플래그 include/hbapi.h 69-99
HB_TYPE typedef include/hbdefs.h 579, 581
HB_MAXINT typedef include/hbdefs.h 442-464
타입 체크 매크로 include/hbapi.h 159-218
숫자 매크로 include/hbvmpub.h 69-109
참조 카운트 include/hbapi.h 520-527
HB_SYMB include/hbvmpub.h 199-214
HB_DYNS include/hbvmpub.h 131-144
산술 연산 src/vm/hvm.c 3285-3770
비교 연산 src/vm/hvm.c 3880-4450
Item API src/vm/itemapi.c 전체
배열 API src/vm/arrays.c 전체
해시 API src/vm/hashes.c 전체
클래스 시스템 src/vm/classes.c 전체
스택 구조 include/hbstack.h 전체

변경 이력

날짜 변경 내용
2026-03-27 초기 작성. Harbour 타입 시스템 분석 및 Go Tagged Value 16B 설계안