C++ 利用宏批量定义类型/常量信息

标题起得比较模糊,是因为我才疏学浅,不知道怎么去描述这个事情

探究的兴趣来源于 V8 的代码:

bytecode.h

它定义了一段我看不懂的宏
[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++ 的预处理器也很硬核。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章