標題起得比較模糊,是因爲我才疏學淺,不知道怎麼去描述這個事情
探究的興趣來源於 V8 的代碼:
它定義了一段我看不懂的宏
[code-0]
#define BYTECODE_LIST(V) \
/* Extended width operands */ \
V(Wide, AccumulatorUse::kNone) \
V(ExtraWide, AccumulatorUse::kNone) \
\
/* Debug Breakpoints - one for each possible size of unscaled bytecodes */ \
/* and one for each operand widening prefix bytecode */ \
V(DebugBreakWide, AccumulatorUse::kReadWrite) \
V(DebugBreakExtraWide, AccumulatorUse::kReadWrite) \
V(DebugBreak0, AccumulatorUse::kReadWrite) \
V(DebugBreak1, AccumulatorUse::kReadWrite, OperandType::kReg) \
V(DebugBreak2, AccumulatorUse::kReadWrite, OperandType::kReg, \
OperandType::kReg) \
V(DebugBreak3, AccumulatorUse::kReadWrite, OperandType::kReg,
//假設它只有這麼長
後來我看到這個宏定義的作用是:
對於每一行 V(...)
,用裏面 ...
的內容作爲 V
的參數,生成一個表達式。例如:
#define FCK(...) fck(...);
BYTECODE_LIST(FCK)
那麼我們相當於得到了這樣一組調用:
fck(Wide, AccumulatorUse::kNone);
fck(ExtraWide, AccumulatorUse::kNone);
fck(DebugBreakWide, AccumulatorUse::kReadWrite);
這裏我們 FCK
宏的定義可以更騷一點,它可以不是函數調用,也可以是別的,比如定義一個類,各位玩的比我好。
有了這個用例,我們就可以做更巧妙的事情。我們可以把一組類型信息(或者常量信息,等等)填入到 [code-0] 這樣的‘宏列表’(這個名字是我自己起的,下面還會繼續用到)中,讓它成爲編譯器可以讀取的信息,從而讓我們把這些長串的信息進行自動化的管理,並且不耗費運行時間。當然,我不知道 C/C++ 有沒有更加直白的方法在編譯器可以做到這一點,接下來是我自己的一個例子,就是用宏列表批量產生類的定義。
下面是一個我模仿 bytecode.h 寫的一段“宏列表”,它的內容不重要,重要在於格式(和規模),需要注意的是
OperandType::Symbol*
, OperandType::Index*
是類型。
namespace IR
{
#define BYTECODE_LIST(C)\
/* visit attribute arg1 of arg0 and load it to accumulator */\
C(LoadAttributeToAcc, AccumulatorUse::Write, OperandType::Symbol*, OperandType::Index*)\
/* call function arg0 with pushed parameters, with return value overwriting Acc */\
C(CallFunc, AccumulatorUse::Write, OperandType::Index*)\
/* push function parameter */\
C(PushParamImm64, AccumulatorUse::None, OperandType::Imm64*)\
C(PushParamSymbol, AccumulatorUse::None, OperandType::Symbol*)\
C(PushParamAcc, AccumulatorUse::Read)\
/* jump to arg0 if Acc is true */\
C(JumpOnTrue, AccumulatorUse::Read, OperandType::Label*)\
/* jump to arg1 if Acc is false */\
C(JumpOnFalse, AccumulatorUse::Write, OperandType::Label*)\
/* logical or with Acc */\
C(Or, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(OrImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* logical and with Acc */\
C(And, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(AndImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* logical equal with Acc */\
C(Equal, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(EqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* logical not-equal with Acc */\
C(NotEqual, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(NotEqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* signed GE with Acc */\
C(GreaterEqual, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(GreaterEqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* signed LE with Acc */\
C(LessEqual, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(LessEqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* signed GT with Acc */\
C(GreaterThan, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(GreaterThanImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* signed LT with Acc */\
C(LessThan, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(LessThanImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* add with Acc */\
C(Add, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(AddImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* subtract with Acc */\
C(Sub, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(SubImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* multiply with Acc */\
C(Mul, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(MulImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* divide with Acc */\
C(Div, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(DivImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* mod with Acc */\
C(Mod, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
C(ModImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
/* incremenmt Acc */\
C(Inc, AccumulatorUse::ReadWrite)\
/* decrement Acc */\
C(Decre, AccumulatorUse::ReadWrite)\
/* negate Acc, that is to multiply with -1 */\
C(Neg, AccumulatorUse::ReadWrite)\
}
接下來我用這樣的宏就可以批量生成名字爲 C{Name}
(比如CInc, CDecre) 這樣的類,並且利用了對應的類型信息:
namespace IR
{
namespace BytecodeClass
{
#define CLASS_DEF(name, acc_use, ...)\
class C##name {\
public:\
void init(__VA_ARGS__);\
void generate_code();\
const char * get_name() {\
return #name;\
}\
IR::AccumulatorUse::Type get_acc_use() {\
return acc_use;\
}\
};
BYTECODE_LIST(CLASS_DEF)
#undef CLASS_DEF
}
}
但是這樣顯然有一個問題:我們在實現的時候,沒有辦法一次把所有類的 init
, generate_code
都實現了,怎麼辦?(如果不實現,編譯時就會有未定義的錯誤)
先在宏裏面批量定義一個定義,然後再重新定義?C++的編譯器不支持重定義這樣的操作,哪怕是編譯期可以確定。利用函數指針是一條路,但是有運行時開銷,這和我們初衷不符,但也不是不行。
後來我發現,我只能發現一條路:將需要定義的方法定義爲虛方法,然後實現的時候逐一繼承這些虛類,這樣既可以利用過程信息,又可以利用我們定義好的宏的內存信息。於是我們把這個宏改一下,需要暫時不定義的地方改成 virtual
,並類名也做了修改:
namespace IR
{
namespace BytecodeClass
{
#define CLASS_DEF(name, acc_use, ...)\
class Virtual##name {\
public:\
virtual void init(__VA_ARGS__) = 0;\
virtual void generate_code() = 0;\
const char * get_name() {\
return #name;\
}\
IR::AccumulatorUse::Type get_acc_use() {\
return acc_use;\
}\
};
BYTECODE_LIST(CLASS_DEF)
#undef CLASS_DEF
}
}
這樣就很爽了,我們實現的時候,就繼承一個虛類出來,然後還可以添加成員屬性,好不快活,並且還能得到自動的類型信息!
智能提示裏的語法信息來自
C(LoadAttributeToAcc, AccumulatorUse::Write, OperandType::Symbol*, OperandType::Index*)\
從信息流動來回顧一下過程,並且驗證一下理解:
1.首先這段信息在“宏列表中被定義”’
2. CLASS_DEF
中,我們通過變長宏參數列表 (name, acc_use, ...)
把 ...
這段,也就是 OperandType::Symbol*, OperandType::Index*
定義了虛函數 init
的參數
3. LoadAttributeToAcc
繼承了宏定義產生的類 VirtualLoadAttributeToAcc
它得到了父類虛函數 init
的參數信息。
不得不說 V8 的工程師是玩的溜。C++ 的預處理器也很硬核。