# 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 물리적 구조](#1-hb_item-물리적-구조) 2. [타입 플래그 맵](#2-타입-플래그-맵) 3. [타입별 상세 분석](#3-타입별-상세-분석) - 3.1 [수치 타입: INTEGER / LONG / DOUBLE](#31-수치-타입-integer--long--double) - 3.2 [문자열: STRING](#32-문자열-string) - 3.3 [날짜/타임스탬프: DATE / TIMESTAMP](#33-날짜타임스탬프-date--timestamp) - 3.4 [배열/객체: ARRAY](#34-배열객체-array) - 3.5 [해시 테이블: HASH](#35-해시-테이블-hash) - 3.6 [코드 블록: BLOCK](#36-코드-블록-block) - 3.7 [참조: BYREF](#37-참조-byref) - 3.8 [심볼: SYMBOL](#38-심볼-symbol) - 3.9 [포인터: POINTER](#39-포인터-pointer) - 3.10 [내부 전용 타입](#310-내부-전용-타입) 4. [GC 통합 구조](#4-gc-통합-구조) 5. [산술 연산 동작](#5-산술-연산-동작) 6. [비교 연산 동작](#6-비교-연산-동작) 7. [Go Value 설계: NaN-boxing 검증 및 기각](#7-go-value-설계-nan-boxing-검증-및-기각) 8. [Go Value 설계: Tagged Value 16B (채택안)](#8-go-value-설계-tagged-value-16b-채택안) 9. [타입별 Go 매핑 상세](#9-타입별-go-매핑-상세) 10. [소스 참조](#10-소스-참조) --- ## 1. HB_ITEM 물리적 구조 ### 정의 위치 - `include/hbapi.h:393-415` ### 구조 ```c 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 예외 복구 - 내부용 ``` ### 복합 타입 그룹 ```c 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 #### 구조 정의 ```c // 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`) ```c // 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 #### 구조 정의 ```c // 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`) ```c #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 #### 구조 정의 ```c // 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 #### 구조 정의 ```c // 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 #### 구조 정의 ```c // 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 다른 타입 키 시도 → 에러 ``` #### 초기 할당 ```c #define HB_HASH_ITEM_ALLOC 16 // 초기 16쌍 할당 ``` --- ### 3.6 코드 블록: BLOCK #### 구조 정의 ```c // 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 #### 구조 정의 ```c // 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`) ```c hb_itemUnRef() // 완전 역참조 (체인 끝까지 따라감) hb_itemUnRefOnce() // 1단계 역참조 hb_itemUnRefRefer() // 마지막 참조만 남김 hb_itemUnRefWrite() // 쓰기용 역참조 (COW 유사) ``` --- ### 3.8 심볼: SYMBOL #### 구조 정의 ```c // 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 #### 구조 정의 ```c // 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`) ```c struct hb_struMemvar { struct _HB_ITEM * value; // PUBLIC/PRIVATE 변수의 실제 값을 가리킴 }; ``` MEMVAR + BYREF 조합으로 사용. 동적 심볼 테이블의 memvar 슬롯 참조. #### ENUM (`include/hbapi.h:356-361`) ```c struct hb_struEnum { struct _HB_ITEM * basePtr; // 반복 대상 (배열/해시/문자열) struct _HB_ITEM * valuePtr; // 현재 값 HB_ISIZ offset; // 현재 인덱스 }; ``` `FOR EACH` 구문의 반복자. VM 내부에서만 사용. #### EXTREF (`include/hbapi.h:363-367`) ```c struct hb_struExtRef { void * value; // 외부 참조 값 const struct _HB_EXTREF * func; // read/write/copy/clear/mark 함수 테이블 }; ``` 확장 참조. 외부 시스템과의 연동용 가상 함수 테이블. #### RECOVER (`include/hbapi.h:384-390`) ```c 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`) ```c 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 에러 발생 ``` ### 오버플로우 감지 패턴 ```c // 덧셈 (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 비교 특이사항 ```c // 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 (채택안) ### 구조 ```go 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 측 보조 구조체 ```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) ```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) ```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 설계안 |