- 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>
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.
목차
- HB_ITEM 물리적 구조
- 타입 플래그 맵
- 타입별 상세 분석
- 3.1 수치 타입: INTEGER / LONG / DOUBLE
- 3.2 문자열: STRING
- 3.3 날짜/타임스탬프: DATE / TIMESTAMP
- 3.4 배열/객체: ARRAY
- 3.5 해시 테이블: HASH
- 3.6 코드 블록: BLOCK
- 3.7 참조: BYREF
- 3.8 심볼: SYMBOL
- 3.9 포인터: POINTER
- 3.10 내부 전용 타입
- GC 통합 구조
- 산술 연산 동작
- 비교 연산 동작
- Go Value 설계: NaN-boxing 검증 및 기각
- Go Value 설계: Tagged Value 16B (채택안)
- 타입별 Go 매핑 상세
- 소스 참조
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-99HB_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 메타데이터의 역할
length와 decimal은 연산에 영향을 주지 않고 표시(formatting)에만 사용된다:
STR(nValue)→length폭으로 포맷STR(nValue, nWidth, nDec)→ 명시적 지정TRANSFORM(),@...SAY→length/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 설계안 |