refactor: 引入 Arena 内存池并优化指令分发,为类型系统重构做准备

This commit is contained in:
2026-03-08 15:59:55 +08:00
parent 91e4eb734e
commit 90448006ff
13 changed files with 301 additions and 36 deletions

View File

@@ -21,6 +21,7 @@ namespace Fig
enum class OpCode : std::uint8_t enum class OpCode : std::uint8_t
{ {
Exit, // 结束运行 Exit, // 结束运行
LoadK, // iABx 模式: R[A] = Constants[Bx] LoadK, // iABx 模式: R[A] = Constants[Bx]
LoadTrue, // iABC: R[A] = true LoadTrue, // iABC: R[A] = true
LoadFalse, // iABC: R[A] = false LoadFalse, // iABC: R[A] = false
@@ -35,7 +36,8 @@ namespace Fig
Jmp, // iAsBx: ip += sBx 无条件跳转 Jmp, // iAsBx: ip += sBx 无条件跳转
JmpIfFalse, // iAsBx: 如果 R[A] 为假, ip += sBx JmpIfFalse, // iAsBx: 如果 R[A] 为假, ip += sBx
Mov, // iABx: R[A] = R[Bx] Mov, // iABx: R[A] = R[Bx]
Add, // iABC: R[A] = R[B] + R[C] Add, // iABC: R[A] = R[B] + R[C]
Sub, // iABC: R[A] = R[B] - R[C] Sub, // iABC: R[A] = R[B] - R[C]
Mul, // iABC: R[A] = R[B] * R[C] Mul, // iABC: R[A] = R[B] * R[C]
@@ -43,6 +45,11 @@ namespace Fig
Mod, // iABC: R[A] = R[B] % R[C] Mod, // iABC: R[A] = R[B] % R[C]
BitXor, // iABC: R[A] = R[B] ^ R[C] BitXor, // iABC: R[A] = R[B] ^ R[C]
IntFastAdd, // iABC: R[A] (Int) = R[B] (Int) + R[C] (Int)
IntFastSub, // iABC: R[A] (Int) = R[B] (Int) - R[C] (Int)
IntFastMul, // iABC: R[A] (Int) = R[B] (Int) * R[C] (Int)
IntFastDiv, // iABC: R[A] (Double) = R[B] (Int) / R[C] (Int)
Equal, // iABC: R[A] = R[B] == R[C] Equal, // iABC: R[A] = R[B] == R[C]
NotEqual, // iABC: R[A] = R[B] != R[C] NotEqual, // iABC: R[A] = R[B] != R[C]
Greater, // iABC: R[A] = R[B] > R[C] Greater, // iABC: R[A] = R[B] > R[C]

View File

@@ -346,7 +346,13 @@ namespace Fig
case OpCode::Sub: case OpCode::Sub:
case OpCode::Mul: case OpCode::Mul:
case OpCode::Div: case OpCode::Div:
case OpCode::Mod: { case OpCode::Mod:
case OpCode::IntFastAdd:
case OpCode::IntFastSub:
case OpCode::IntFastMul:
case OpCode::IntFastDiv:
{
// iABC 模式:解析 B (16~23 位) 和 C (24~31 位) // iABC 模式:解析 B (16~23 位) 和 C (24~31 位)
std::uint8_t b = (inst >> 16) & 0xFF; std::uint8_t b = (inst >> 16) & 0xFF;
std::uint8_t c = (inst >> 24) & 0xFF; std::uint8_t c = (inst >> 24) & 0xFF;

View File

@@ -139,13 +139,16 @@ namespace Fig
return compileAssignment(infix); return compileAssignment(infix);
} }
const auto &_lhsReg = compileExpr(infix->left); Expr *left = infix->left;
Expr *right = infix->right;
const auto &_lhsReg = compileExpr(left);
if (!_lhsReg) if (!_lhsReg)
{ {
return _lhsReg; return _lhsReg;
} }
std::uint8_t lhsReg = *_lhsReg; std::uint8_t lhsReg = *_lhsReg;
const auto &_rhsReg = compileExpr(infix->right); const auto &_rhsReg = compileExpr(right);
if (!_rhsReg) if (!_rhsReg)
{ {
return _rhsReg; return _rhsReg;
@@ -156,25 +159,58 @@ namespace Fig
FreeReg(lhsReg); FreeReg(lhsReg);
std::uint8_t resultReg = AllocReg(); std::uint8_t resultReg = AllocReg();
switch (infix->op) switch (infix->op)
{ {
case BinaryOperator::Add: { case BinaryOperator::Add: {
Emit(Op::iABC(OpCode::Add, resultReg, lhsReg, rhsReg)); if (left->resolvedType == right->resolvedType && left->resolvedType->isInt())
{
// Int + Int
Emit(Op::iABC(OpCode::IntFastAdd, resultReg, lhsReg, rhsReg));
}
else
{
Emit(Op::iABC(OpCode::Add, resultReg, lhsReg, rhsReg));
}
break; break;
} }
case BinaryOperator::Subtract: { case BinaryOperator::Subtract: {
Emit(Op::iABC(OpCode::Sub, resultReg, lhsReg, rhsReg)); if (left->resolvedType == right->resolvedType && left->resolvedType->isInt())
{
// Int - Int
Emit(Op::iABC(OpCode::IntFastSub, resultReg, lhsReg, rhsReg));
}
else
{
Emit(Op::iABC(OpCode::Sub, resultReg, lhsReg, rhsReg));
}
break; break;
} }
case BinaryOperator::Multiply: { case BinaryOperator::Multiply: {
Emit(Op::iABC(OpCode::Mul, resultReg, lhsReg, rhsReg)); if (left->resolvedType == right->resolvedType && left->resolvedType->isInt())
{
// Int * Int
Emit(Op::iABC(OpCode::IntFastMul, resultReg, lhsReg, rhsReg));
}
else
{
Emit(Op::iABC(OpCode::Mul, resultReg, lhsReg, rhsReg));
}
break; break;
} }
case BinaryOperator::Divide: { case BinaryOperator::Divide: {
Emit(Op::iABC(OpCode::Div, resultReg, lhsReg, rhsReg)); if (left->resolvedType == right->resolvedType && left->resolvedType->isInt())
{
// Int / Int
Emit(Op::iABC(OpCode::IntFastDiv, resultReg, lhsReg, rhsReg));
}
else
{
Emit(Op::iABC(OpCode::Div, resultReg, lhsReg, rhsReg));
}
break; break;
} }
@@ -263,11 +299,11 @@ namespace Fig
Emit(Op::iABx(OpCode::Mov, baseReg, *calleeRes)); Emit(Op::iABx(OpCode::Mov, baseReg, *calleeRes));
} }
} }
for (size_t i = 0; i < expr->args.size(); ++i) for (size_t i = 0; i < expr->args.size(); ++i)
{ {
std::uint8_t argTarget = AllocReg(); std::uint8_t argTarget = AllocReg();
auto argRes = compileExpr(expr->args.args[i]); auto argRes = compileExpr(expr->args.args[i]);
if (!argRes) if (!argRes)
{ {
return argRes; return argRes;

View File

@@ -41,7 +41,9 @@ namespace Fig::CoreIO
void InitConsoleIO() void InitConsoleIO()
{ {
#ifdef _WIN32
SetConsoleCP(CP_UTF8); SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
#endif
} }
}; };

View File

@@ -9,7 +9,6 @@
#include <Object/ObjectBase.hpp> #include <Object/ObjectBase.hpp>
namespace Fig namespace Fig
{ {
// 运行时闭包对象 (24字节 Base + 8字节 Proto指针 = 32 bytes) // 运行时闭包对象 (24字节 Base + 8字节 Proto指针 = 32 bytes)
@@ -17,7 +16,9 @@ namespace Fig
struct Proto; struct Proto;
struct FunctionObject final : public Object struct FunctionObject final : public Object
{ {
String name; // 调试使用
Proto *proto; // 指向编译器生成的只读字节码与常量池 Proto *proto; // 指向编译器生成的只读字节码与常量池
std::uint8_t paraCount;
// TODO: 实现闭包时 加一个 Upvalue 指针数组 // TODO: 实现闭包时 加一个 Upvalue 指针数组
// Value* upvalues; // Value* upvalues;

View File

@@ -14,7 +14,7 @@ namespace Fig
StateProtector p(this, {State::ParsingLiteralExpr}); StateProtector p(this, {State::ParsingLiteralExpr});
const Token &literal_token = consumeToken(); const Token &literal_token = consumeToken();
LiteralExpr *node = new LiteralExpr(literal_token, makeSourceLocation(literal_token)); LiteralExpr *node = arena.Allocate<LiteralExpr>(literal_token, makeSourceLocation(literal_token));
return node; return node;
} }
Result<IdentiExpr *, Error> Parser::parseIdentiExpr() // 当前token为Identifier调用 Result<IdentiExpr *, Error> Parser::parseIdentiExpr() // 当前token为Identifier调用
@@ -22,7 +22,7 @@ namespace Fig
StateProtector p(this, {State::ParsingIdentiExpr}); StateProtector p(this, {State::ParsingIdentiExpr});
const Token &identifier = consumeToken(); const Token &identifier = consumeToken();
IdentiExpr *node = new IdentiExpr( IdentiExpr *node = arena.Allocate<IdentiExpr>(
srcManager.GetSub(identifier.index, identifier.length), makeSourceLocation(identifier)); srcManager.GetSub(identifier.index, identifier.length), makeSourceLocation(identifier));
return node; return node;
} }
@@ -42,7 +42,7 @@ namespace Fig
} }
Expr *rhs = *rhs_result; Expr *rhs = *rhs_result;
InfixExpr *node = new InfixExpr(lhs, op, rhs); InfixExpr *node = arena.Allocate<InfixExpr>(lhs, op, rhs);
return node; return node;
} }
@@ -61,7 +61,7 @@ namespace Fig
} }
Expr *rhs = *rhs_result; Expr *rhs = *rhs_result;
PrefixExpr *node = new PrefixExpr(op, rhs); PrefixExpr *node = arena.Allocate<PrefixExpr>(op, rhs);
return node; return node;
} }
@@ -87,7 +87,7 @@ namespace Fig
} }
consumeToken(); // consume `]` consumeToken(); // consume `]`
IndexExpr *indexExpr = new IndexExpr(base, *index_result); IndexExpr *indexExpr = arena.Allocate<IndexExpr>(base, *index_result);
return indexExpr; return indexExpr;
} }
@@ -104,7 +104,7 @@ namespace Fig
if (currentToken().type == TokenType::RightParen) if (currentToken().type == TokenType::RightParen)
{ {
consumeToken(); // consume `)` consumeToken(); // consume `)`
return new CallExpr(callee, callArgs); return arena.Allocate<CallExpr>(callee, callArgs);
} }
while (true) while (true)
@@ -140,7 +140,7 @@ namespace Fig
consumeToken(); // consume `,` consumeToken(); // consume `,`
} }
return new CallExpr(callee, callArgs); return arena.Allocate<CallExpr>(callee, callArgs);
} }
Result<Expr *, Error> Parser::parseExpression(BindingPower rbp) Result<Expr *, Error> Parser::parseExpression(BindingPower rbp)

View File

@@ -11,7 +11,7 @@ namespace Fig
{ {
Result<Program *, Error> Parser::Parse() Result<Program *, Error> Parser::Parse()
{ {
Program *program = new Program; Program *program = arena.Allocate<Program>();
while (!isEOF) while (!isEOF)
{ {
auto result = parseStatement(); auto result = parseStatement();
@@ -27,5 +27,6 @@ namespace Fig
program->nodes.push_back(stmt); program->nodes.push_back(stmt);
} }
return program; return program;
} }
}; // namespace Fig }; // namespace Fig

View File

@@ -12,6 +12,7 @@
#include <Error/Error.hpp> #include <Error/Error.hpp>
#include <Lexer/Lexer.hpp> #include <Lexer/Lexer.hpp>
#include <Token/Token.hpp> #include <Token/Token.hpp>
#include <Utils/Arena.hpp>
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
@@ -24,6 +25,7 @@ namespace Fig
class Parser class Parser
{ {
private: private:
Arena arena;
Lexer &lexer; Lexer &lexer;
SourceManager &srcManager; SourceManager &srcManager;

View File

@@ -12,7 +12,7 @@ namespace Fig
Result<BlockStmt *, Error> Parser::parseBlockStmt() // 当前token为 { Result<BlockStmt *, Error> Parser::parseBlockStmt() // 当前token为 {
{ {
SourceLocation location = makeSourceLocation(consumeToken()); // consume `{` SourceLocation location = makeSourceLocation(consumeToken()); // consume `{`
BlockStmt *stmt = new BlockStmt(); BlockStmt *stmt = arena.Allocate<BlockStmt>();
while (true) while (true)
{ {
if (isEOF) if (isEOF)
@@ -94,7 +94,7 @@ namespace Fig
{ {
return std::unexpected(makeExpectSemicolonError()); return std::unexpected(makeExpectSemicolonError());
} }
VarDecl *varDecl = new VarDecl(isPublic, name, typeSpeicifer, isInfer, initExpr, location); VarDecl *varDecl = arena.Allocate<VarDecl>(isPublic, name, typeSpeicifer, isInfer, initExpr, location);
return varDecl; return varDecl;
} }
@@ -207,7 +207,7 @@ namespace Fig
return std::unexpected(result.error()); return std::unexpected(result.error());
} }
BlockStmt *consequent = *result; BlockStmt *consequent = *result;
ElseIfStmt *elif = new ElseIfStmt(cond, consequent, elseLocation); ElseIfStmt *elif = arena.Allocate<ElseIfStmt>(cond, consequent, elseLocation);
elifs.push_back(elif); elifs.push_back(elif);
} }
else else
@@ -233,7 +233,7 @@ namespace Fig
alternate = *result; alternate = *result;
} }
} }
IfStmt *ifStmt = new IfStmt(cond, consequent, elifs, alternate, location); IfStmt *ifStmt = arena.Allocate<IfStmt>(cond, consequent, elifs, alternate, location);
return ifStmt; return ifStmt;
} }
@@ -291,7 +291,7 @@ namespace Fig
} }
BlockStmt *body = *result; BlockStmt *body = *result;
WhileStmt *whileStmt = new WhileStmt(cond, body, location); WhileStmt *whileStmt = arena.Allocate<WhileStmt>(cond, body, location);
return whileStmt; return whileStmt;
} }
@@ -350,7 +350,7 @@ namespace Fig
defaultValue = *result; defaultValue = *result;
} }
PosParam *posParam = new PosParam(name, type, defaultValue, location); PosParam *posParam = arena.Allocate<PosParam>(name, type, defaultValue, location);
params.push_back(posParam); params.push_back(posParam);
if (match(TokenType::Comma)) if (match(TokenType::Comma))
@@ -423,7 +423,7 @@ namespace Fig
} }
body = *bodyResult; body = *bodyResult;
FnDefStmt *fnDef = new FnDefStmt(isPublic, name, params, returnType, body, location); FnDefStmt *fnDef = arena.Allocate<FnDefStmt>(isPublic, name, params, returnType, body, location);
return fnDef; return fnDef;
} }
@@ -440,7 +440,7 @@ namespace Fig
} }
Expr *value = *result; Expr *value = *result;
ReturnStmt *returnStmt = new ReturnStmt(value, location); ReturnStmt *returnStmt = arena.Allocate<ReturnStmt>(value, location);
if (!match(TokenType::Semicolon)) if (!match(TokenType::Semicolon))
{ {
@@ -507,7 +507,7 @@ namespace Fig
{ {
return std::unexpected(makeExpectSemicolonError()); return std::unexpected(makeExpectSemicolonError());
} }
BreakStmt *breakStmt = new BreakStmt(location); BreakStmt *breakStmt = arena.Allocate<BreakStmt>(location);
return breakStmt; return breakStmt;
} }
@@ -518,7 +518,7 @@ namespace Fig
{ {
return std::unexpected(makeExpectSemicolonError()); return std::unexpected(makeExpectSemicolonError());
} }
ContinueStmt *continueStmt = new ContinueStmt(location); ContinueStmt *continueStmt = arena.Allocate<ContinueStmt>(location);
return continueStmt; return continueStmt;
} }
@@ -532,7 +532,7 @@ namespace Fig
{ {
return std::unexpected(expr_result.error()); return std::unexpected(expr_result.error());
} }
ExprStmt *exprStmt = new ExprStmt(*expr_result); ExprStmt *exprStmt = arena.Allocate<ExprStmt>(*expr_result);
if (!match(TokenType::Semicolon)) if (!match(TokenType::Semicolon))
{ {
return std::unexpected(makeExpectSemicolonError()); return std::unexpected(makeExpectSemicolonError());

View File

@@ -5,8 +5,6 @@
@date 2026-02-25 @date 2026-02-25
*/ */
#pragma once
#include <Parser/Parser.hpp> #include <Parser/Parser.hpp>
namespace Fig namespace Fig
@@ -37,7 +35,7 @@ namespace Fig
break; break;
} }
} }
NamedTypeExpr *namedTypeExpr = new NamedTypeExpr(path, location); NamedTypeExpr *namedTypeExpr = arena.Allocate<NamedTypeExpr>(path, location);
return namedTypeExpr; return namedTypeExpr;
} }

View File

@@ -33,6 +33,41 @@ namespace Fig
{ {
return tag == TypeTag::Any; return tag == TypeTag::Any;
} }
bool isNull() const
{
return tag == TypeTag::Null;
}
bool isInt() const
{
return tag == TypeTag::Int;
}
bool isDouble() const
{
return tag == TypeTag::Double;
}
bool isBool() const
{
return tag == TypeTag::Bool;
}
bool isString() const
{
return tag == TypeTag::String;
}
bool isFunction() const
{
return tag == TypeTag::Function;
}
bool isStruct() const
{
return tag == TypeTag::Struct;
}
}; };
// 全局唯一类型驻留池 // 全局唯一类型驻留池

115
src/Utils/Arena.hpp Normal file
View File

@@ -0,0 +1,115 @@
/*!
@file src/Utils/Arena.hpp
@brief 线性分配内存池,支持非平凡析构对象的自动清理
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <cstddef>
#include <vector>
#include <memory>
#include <type_traits>
namespace Fig
{
class Arena
{
private:
struct DestructorNode
{
void (*destructor)(void *);
void *object;
DestructorNode *next;
};
static constexpr std::size_t CHUNK_SIZE = 64 * 1024; // 64KB 块大小
std::vector<char *> chunks;
char *currentPtr = nullptr;
std::size_t remaining = 0;
DestructorNode *destructorHead = nullptr;
public:
Arena() = default;
~Arena()
{
// 1. 逆序调用析构函数
DestructorNode *node = destructorHead;
while (node)
{
node->destructor(node->object);
node = node->next;
}
// 2. 释放所有分配的内存块
for (char *chunk : chunks)
{
delete[] chunk;
}
}
// 禁止拷贝和移动,防止内存所有权混乱
Arena(const Arena &) = delete;
Arena &operator=(const Arena &) = delete;
template <typename T, typename... Args>
T *Allocate(Args &&...args)
{
std::size_t size = sizeof(T);
std::size_t alignment = alignof(T);
// 在当前块中尝试对齐并分配
void *ptr = allocateRaw(size, alignment);
// 在分配的内存上构造对象
T *obj = new (ptr) T(std::forward<Args>(args)...);
// 如果 T 需要析构(如包含 String注册到销毁链表
if constexpr (!std::is_trivially_destructible_v<T>)
{
// 注意: DestructorNode 本身是 POD直接在 Arena 里分配,不需要注册析构
void *nodeRaw = allocateRaw(sizeof(DestructorNode), alignof(DestructorNode));
DestructorNode *node = new (nodeRaw) DestructorNode();
node->object = obj;
node->destructor = [](void *p) { static_cast<T *>(p)->~T(); };
node->next = destructorHead;
destructorHead = node;
}
return obj;
}
private:
void *allocateRaw(std::size_t size, std::size_t alignment)
{
// 对齐计算
std::size_t adjustment = 0;
std::size_t currentAddr = reinterpret_cast<std::size_t>(currentPtr);
if (alignment > 0 && (currentAddr % alignment) != 0)
{
adjustment = alignment - (currentAddr % alignment);
}
if (remaining < (size + adjustment))
{
// 当前块空间不足,分配新块
std::size_t nextChunkSize = (size > CHUNK_SIZE) ? size : CHUNK_SIZE;
currentPtr = new char[nextChunkSize];
chunks.push_back(currentPtr);
remaining = nextChunkSize;
adjustment = 0; // 新分配的块通常是最大对齐的
}
currentPtr += adjustment;
void *res = currentPtr;
currentPtr += size;
remaining -= (size + adjustment);
return res;
}
};
} // namespace Fig

View File

@@ -86,24 +86,37 @@ namespace Fig
pushFrame(entry, registers); pushFrame(entry, registers);
// 🔥 必须与 Bytecode.hpp 中的 OpCode 枚举严格一一对应! // 🔥 必须与 Bytecode.hpp 中的 OpCode 枚举严格一一对应!
static const void *dispatchTable[] = {&&do_Exit, static const void *dispatchTable[] = {
&&do_Exit,
&&do_LoadK, &&do_LoadK,
&&do_LoadTrue, &&do_LoadTrue,
&&do_LoadFalse, &&do_LoadFalse,
&&do_LoadNull, &&do_LoadNull,
&&do_FastCall, &&do_FastCall,
&&do_Call, &&do_Call,
&&do_Return, &&do_Return,
&&do_LoadFn, &&do_LoadFn,
&&do_Jmp, &&do_Jmp,
&&do_JmpIfFalse, &&do_JmpIfFalse,
&&do_Mov, &&do_Mov,
&&do_Add, &&do_Add,
&&do_Sub, &&do_Sub,
&&do_Mul, &&do_Mul,
&&do_Div, &&do_Div,
&&do_Mod, &&do_Mod,
&&do_BitXor, &&do_BitXor,
&&do_IntFastAdd,
&&do_IntFastSub,
&&do_IntFastMul,
&&do_IntFastDiv,
&&do_Equal, &&do_Equal,
&&do_NotEqual, &&do_NotEqual,
&&do_Greater, &&do_Greater,
@@ -114,7 +127,7 @@ namespace Fig
Instruction inst; Instruction inst;
// 🔥 核心分发引擎:取指 -> 直接查表并 Jump // 取指 -> 直接查表并 Jump
#define DISPATCH() \ #define DISPATCH() \
do \ do \
{ \ { \
@@ -122,7 +135,7 @@ namespace Fig
goto *dispatchTable[inst & 0xFF]; \ goto *dispatchTable[inst & 0xFF]; \
} while (0) } while (0)
// 引擎点火 // 引擎点火!! :3
DISPATCH(); DISPATCH();
do_Exit: { do_Exit: {
@@ -215,6 +228,54 @@ namespace Fig
assert(false && "VM: Mod and BitXor not fully implemented yet!"); assert(false && "VM: Mod and BitXor not fully implemented yet!");
DISPATCH(); DISPATCH();
do_IntFastAdd: {
std::uint8_t a = decodeA(inst);
std::uint8_t b = decodeB(inst);
std::uint8_t c = decodeC(inst);
Value l = currentFrame->registerBase[b];
Value r = currentFrame->registerBase[c];
currentFrame->registerBase[a] = Value::FromInt(l.AsInt() + r.AsInt());
DISPATCH();
}
do_IntFastSub: {
std::uint8_t a = decodeA(inst);
std::uint8_t b = decodeB(inst);
std::uint8_t c = decodeC(inst);
Value l = currentFrame->registerBase[b];
Value r = currentFrame->registerBase[c];
currentFrame->registerBase[a] = Value::FromInt(l.AsInt() - r.AsInt());
DISPATCH();
}
do_IntFastMul: {
std::uint8_t a = decodeA(inst);
std::uint8_t b = decodeB(inst);
std::uint8_t c = decodeC(inst);
Value l = currentFrame->registerBase[b];
Value r = currentFrame->registerBase[c];
currentFrame->registerBase[a] = Value::FromInt(l.AsInt() * r.AsInt());
DISPATCH();
}
do_IntFastDiv: {
std::uint8_t a = decodeA(inst);
std::uint8_t b = decodeB(inst);
std::uint8_t c = decodeC(inst);
Value l = currentFrame->registerBase[b];
Value r = currentFrame->registerBase[c];
currentFrame->registerBase[a] = Value::FromDouble(l.AsInt() + r.AsInt());
DISPATCH();
}
BINARY_COMPARE_OP(Equal, ==); BINARY_COMPARE_OP(Equal, ==);
BINARY_COMPARE_OP(NotEqual, !=); BINARY_COMPARE_OP(NotEqual, !=);
BINARY_COMPARE_OP(Greater, >); BINARY_COMPARE_OP(Greater, >);
@@ -226,5 +287,6 @@ namespace Fig
assert(false && "Hit Count sentinel!"); assert(false && "Hit Count sentinel!");
return Value::GetNullInstance(); return Value::GetNullInstance();
} }
} }
}; // namespace Fig }; // namespace Fig