Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

(initial)

  • Loading branch information...
commit a2f05146a6a54fc54fe14a7b97eebf72747a3af4 0 parents
Fedor Indutny authored
Showing with 2,581 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +7 −0 deps/hogan.jit/.gitignore
  3. +2 −0  deps/hogan.jit/.travis.yml
  4. +68 −0 deps/hogan.jit/Makefile
  5. +1 −0  deps/hogan.jit/README.md
  6. +59 −0 deps/hogan.jit/include/hogan.h
  7. +129 −0 deps/hogan.jit/src/assembler.h
  8. +54 −0 deps/hogan.jit/src/codegen.h
  9. +128 −0 deps/hogan.jit/src/compiler.cc
  10. +65 −0 deps/hogan.jit/src/compiler.h
  11. +49 −0 deps/hogan.jit/src/hogan.cc
  12. +150 −0 deps/hogan.jit/src/ia32/assembler-ia32.cc
  13. +19 −0 deps/hogan.jit/src/ia32/assembler-ia32.h
  14. +297 −0 deps/hogan.jit/src/ia32/codegen-ia32.cc
  15. +151 −0 deps/hogan.jit/src/lexer.h
  16. +183 −0 deps/hogan.jit/src/output.h
  17. +52 −0 deps/hogan.jit/src/parser.cc
  18. +102 −0 deps/hogan.jit/src/parser.h
  19. +61 −0 deps/hogan.jit/src/queue.h
  20. +164 −0 deps/hogan.jit/src/x64/assembler-x64.cc
  21. +27 −0 deps/hogan.jit/src/x64/assembler-x64.h
  22. +277 −0 deps/hogan.jit/src/x64/codegen-x64.cc
  23. +71 −0 deps/hogan.jit/test/bench-basic.cc
  24. +118 −0 deps/hogan.jit/test/test-api.cc
  25. +40 −0 deps/hogan.jit/test/test-output-realloc.cc
  26. +32 −0 deps/hogan.jit/test/test-partials.cc
  27. +47 −0 deps/hogan.jit/test/test.h
  28. +3 −0  lib/hogan.jit.js
  29. +6 −0 lib/hogan.jit/core.js
  30. +14 −0 package.json
  31. +103 −0 src/node_hogan.cc
  32. +38 −0 src/node_hogan.h
  33. +22 −0 test/api-test.js
  34. +38 −0 wscript
4 .gitignore
@@ -0,0 +1,4 @@
+build
+.lock-wscript
+*.node
+node_modules/
7 deps/hogan.jit/.gitignore
@@ -0,0 +1,7 @@
+*.a
+*.o
+*.dSYM
+test/test-api
+test/test-partials
+test/test-output-realloc
+test/bench-basic
2  deps/hogan.jit/.travis.yml
@@ -0,0 +1,2 @@
+script:
+ - "make -j4 -B test && make -j4 -B MODE=release test"
68 deps/hogan.jit/Makefile
@@ -0,0 +1,68 @@
+CPPFLAGS += -Wall -Wextra -Wno-unused-parameter
+CPPFLAGS += -fPIC -Iinclude
+CPPFLAGS += -fno-strict-aliasing
+CPPFLAGS += -g
+
+ifeq ($(shell sh -c 'uname -s 2>/dev/null'),Darwin)
+ OS = Darwin
+else
+ OS = Linux
+endif
+
+ifeq ($(MODE),release)
+ CPPFLAGS += -O3
+ CPPFLAGS += -DNDEBUG
+endif
+
+ifeq ($(ARCH),)
+ ARCH = $(shell sh -c 'uname -m | sed -e "s/i.86/i386/;s/x86_64/x64/;s/amd64/x64/"')
+endif
+
+all: hogan.a
+
+OBJS += src/hogan.o
+OBJS += src/parser.o
+OBJS += src/compiler.o
+
+ifeq ($(ARCH),i386)
+ ifeq ($(OS),Darwin)
+ CPPFLAGS += -arch i386
+ else
+ CPPFLAGS += -m32
+ endif
+ OBJS += src/ia32/assembler-ia32.o
+ OBJS += src/ia32/codegen-ia32.o
+else
+ OBJS += src/x64/assembler-x64.o
+ OBJS += src/x64/codegen-x64.o
+endif
+
+ifeq ($(OS),Darwin)
+ CPPFLAGS += -D__DARWIN
+else
+ CPPFLAGS += -D__LINUX
+endif
+
+hogan.a: $(OBJS)
+ $(AR) rcs hogan.a $(OBJS)
+
+src/%.o: src/%.cc
+ $(CXX) $(CPPFLAGS) -Isrc -c $< -o $@
+
+TESTS += test/test-api
+TESTS += test/test-output-realloc
+TESTS += test/test-partials
+TESTS += test/bench-basic
+
+test: $(TESTS)
+ @test/test-api
+ @test/test-output-realloc
+ @test/test-partials
+
+test/%: test/%.cc hogan.a
+ $(CXX) $(CPPFLAGS) $< -o $@ hogan.a
+
+clean:
+ rm -f $(OBJS) hogan.a
+
+.PHONY: all test
1  deps/hogan.jit/README.md
@@ -0,0 +1 @@
+[![Build Status](https://secure.travis-ci.org/indutny/hogan.jit.png)](http://travis-ci.org/indutny/hogan.jit)
59 deps/hogan.jit/include/hogan.h
@@ -0,0 +1,59 @@
+#ifndef _HOGAN_H_
+#define _HOGAN_H_
+
+namespace hogan {
+
+class Template;
+class TemplateCode;
+class Compiler;
+class Codegen;
+class Options;
+
+class Hogan {
+ public:
+ Hogan(Options* options);
+ ~Hogan();
+
+ Template* Compile(const char* source);
+ private:
+ Options* options_;
+};
+
+typedef const void* (*PropertyCallback)(void* obj, const char* key);
+typedef const void* (*NumericPropertyCallback)(void* obj, const int index);
+typedef int (*IsArrayCallback)(void* obj);
+typedef Template* (*PartialCallback)(Template* tpl, const char* name);
+
+class Options {
+ public:
+ Options(PropertyCallback getString_,
+ PropertyCallback getObject_,
+ NumericPropertyCallback at_,
+ IsArrayCallback isArray_,
+ PartialCallback getPartial_);
+ Options();
+
+ PropertyCallback getString;
+ PropertyCallback getObject;
+ NumericPropertyCallback at;
+ IsArrayCallback isArray;
+ PartialCallback getPartial;
+};
+
+
+class Template {
+ public:
+ Template();
+ ~Template();
+
+ char* Render(void* obj);
+
+ private:
+ TemplateCode* code;
+ friend class Compiler;
+ friend class Codegen;
+};
+
+} // namespace hogan
+
+#endif // _HOGAN_H_
129 deps/hogan.jit/src/assembler.h
@@ -0,0 +1,129 @@
+#ifndef _SRC_ASSEMBLER_H_
+#define _SRC_ASSEMBLER_H_
+
+#include "compiler.h"
+#include <stdint.h> // uintXX_t
+
+namespace hogan {
+
+class Assembler {
+ public:
+ Assembler(Code* code_) : code(code_), offset(0) {
+ }
+
+ class Label {
+ public:
+ struct Pos {
+ int32_t offset;
+ uint8_t size;
+ };
+
+ Label() : offset(-1) {
+ }
+
+ int32_t offset;
+ Queue<Pos*> queue;
+ };
+
+ inline void Immediate(uint64_t imm) {
+ *reinterpret_cast<uint64_t*>(pos()) = imm;
+ offset += sizeof(imm);
+ }
+
+
+ inline void Immediate(const uint32_t imm) {
+ *reinterpret_cast<uint32_t*>(pos()) = imm;
+ offset += sizeof(imm);
+ }
+
+
+ inline void Immediate(const uint16_t imm) {
+ *reinterpret_cast<uint16_t*>(pos()) = imm;
+ offset += sizeof(imm);
+ }
+
+
+ inline void Immediate(const uint8_t imm) {
+ *reinterpret_cast<uint8_t*>(pos()) = imm;
+ offset += sizeof(imm);
+ }
+
+
+ inline void Immediate(const uint32_t imm, const uint8_t size) {
+ if (size == 1) {
+ Immediate(static_cast<const uint8_t>(imm));
+ } else if (size == 2) {
+ Immediate(static_cast<const uint16_t>(imm));
+ } else if (size == 4) {
+ Immediate(static_cast<const uint32_t>(imm));
+ } else if (size == 8) {
+ Immediate(static_cast<const uint64_t>(imm));
+ }
+ }
+
+
+ void Push(int reg);
+ void PushImm(uint32_t imm);
+ void Pop(int reg);
+ void Mov(int dst, int src);
+ void MovToContext(uint8_t offset, int src);
+ void MovFromContext(int dst, uint8_t offset);
+ void MovImm(int dst, uint64_t imm);
+ void AddImm(int dst, uint8_t imm);
+ void AddImmToContext(int offset, uint32_t imm);
+ void AddToContext(int offset, int src);
+ void SubImm(int dst, uint8_t imm);
+ void Inc(int dst);
+ void Xor(int dst, int src);
+ int PreCall(int offset, int args);
+ void Call(const void* addr);
+ void Leave();
+ void Return(uint16_t bytes);
+ void Cmp(int src, uint32_t imm);
+ void Je(Label* lbl);
+ void Jmp(Label* lbl);
+
+ inline void Bind(Label* lbl) {
+ assert(lbl->offset == -1);
+
+ lbl->offset = offset;
+ Label::Pos* pos;
+ while ((pos = lbl->queue.Shift()) != NULL) {
+ offset = pos->offset;
+ Offset(lbl, pos->size);
+
+ delete pos;
+ }
+ offset = lbl->offset;
+ }
+
+ inline void Offset(Label* lbl, int8_t size) {
+ if (lbl->offset == -1) {
+ Label::Pos* pos = new Label::Pos();
+ pos->offset = offset;
+ pos->size = size;
+
+ lbl->queue.Push(pos);
+
+ offset += size;
+ } else {
+ Immediate(lbl->offset - offset - size, size);
+ }
+ }
+
+ inline void emit(int byte) {
+ (reinterpret_cast<unsigned char*>(code->code) + offset++)[0] = byte;
+ if (static_cast<uint32_t>(offset + 16) >= code->size) code->Grow();
+ }
+
+ inline char* pos() {
+ return code->code + offset;
+ }
+
+ Code* code;
+ int32_t offset;
+};
+
+} // namespace hogan
+
+#endif // _SRC_ASSEMBLER_H_
54 deps/hogan.jit/src/codegen.h
@@ -0,0 +1,54 @@
+#ifndef _SRC_CODEGEN_H_
+#define _SRC_CODEGEN_H_
+
+#include "hogan.h"
+#include "compiler.h"
+#include "assembler.h"
+#include "parser.h"
+
+#include <string.h> // memcpy
+
+namespace hogan {
+
+class Code;
+
+class Codegen : public Assembler {
+ public:
+ Codegen(Code* code_, Options* options_) : Assembler(code_),
+ options(options_) {
+ data = new Queue<char*>();
+ }
+
+ void GeneratePrologue();
+ void GenerateEpilogue();
+
+ void GenerateBlock(AstNode* node);
+ void GenerateString(AstNode* node);
+ void GenerateProp(AstNode* node, bool escape);
+ void GenerateIf(AstNode* node);
+ void GeneratePartial(AstNode* node);
+
+ typedef void (TemplateOutput::*PushCallback)(const char*,
+ const size_t,
+ const size_t);
+ typedef void (*InvokePartialType)(Template*, void*, TemplateOutput*);
+ static void InvokePartial(Template* t, void* obj, TemplateOutput* out) {
+ t->code->AsFunction()(obj, out, t);
+ }
+
+ inline char* ToData(AstNode* node) {
+ char* value = new char[node->length + 1];
+ memcpy(value, node->value, node->length);
+ value[node->length] = 0;
+ data->Push(value);
+
+ return value;
+ }
+
+ Queue<char*>* data;
+ Options* options;
+};
+
+} // namespace hogan
+
+#endif // _SRC_CODEGEN_H_
128 deps/hogan.jit/src/compiler.cc
@@ -0,0 +1,128 @@
+#include "hogan.h"
+#include "compiler.h"
+#include "codegen.h"
+#include "output.h" // TemplateOutput
+
+#include <assert.h> // assert
+#include <stdlib.h> // NULL, abort
+#include <string.h> // memset
+#include <sys/mman.h> // mmap, munmap
+#include <sys/types.h> // size_t
+#include <stdint.h> // uint32_t
+
+#include <unistd.h> // sysconf or getpagesize
+
+namespace hogan {
+
+Template* Compiler::Compile(AstNode* ast,
+ Options* options,
+ const char* source) {
+ Template* t = new Template();
+
+ t->code->source = source;
+ Codegen codegen(t->code, options);
+
+ codegen.GeneratePrologue();
+ codegen.GenerateBlock(ast);
+ codegen.GenerateEpilogue();
+
+ t->code->data = codegen.data;
+
+ // Copy buffered code into executable memory
+ // and guard it
+ t->code->Commit();
+
+ return t;
+}
+
+
+char* Template::Render(void* obj) {
+ TemplateOutput out;
+ code->AsFunction()(obj, &out, this);
+
+ return out.Join();
+};
+
+
+Template::Template() {
+ code = new TemplateCode();
+}
+
+
+Template::~Template() {
+ delete code;
+}
+
+
+Code::Code() {
+#ifdef __DARWIN
+ page_size = getpagesize();
+#else
+ page_size = sysconf(_SC_PAGE_SIZE);
+#endif
+
+ size = page_size;
+ guard = NULL;
+
+ code = new char[size];
+ committed = false;
+ memset(code, 0x90, size);
+}
+
+
+Code::~Code() {
+ if (committed) {
+ if (munmap(code, static_cast<size_t>(size)) != 0) abort();
+ if (munmap(guard, static_cast<size_t>(page_size)) != 0) abort();
+ } else {
+ delete code;
+ }
+
+ char* chunk;
+ while ((chunk = data->Shift()) != NULL) delete chunk;
+ delete data;
+}
+
+
+void Code::Grow() {
+ uint32_t new_size = size << 1;
+ char* new_code = new char[new_size];
+
+ memcpy(new_code, code, size);
+ memset(new_code + size, 0x90, size);
+
+ delete code;
+ code = new_code;
+}
+
+
+void Code::Commit() {
+ void* data = mmap(0,
+ size,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_ANON | MAP_PRIVATE,
+ -1,
+ 0);
+
+ if (data == MAP_FAILED) abort();
+
+ memcpy(data, code, size);
+ delete code;
+ code = reinterpret_cast<char*>(data);
+
+ // Allocate guard
+ data = mmap(code + size,
+ page_size,
+ PROT_NONE,
+ MAP_ANON | MAP_PRIVATE,
+ -1,
+ 0);
+ if (data == MAP_FAILED) abort();
+
+ guard = reinterpret_cast<char*>(data);
+
+ committed = true;
+}
+
+
+} // namespace hogan
65 deps/hogan.jit/src/compiler.h
@@ -0,0 +1,65 @@
+#ifndef _SRC_ASM_H_
+#define _SRC_ASM_H_
+
+#include "hogan.h"
+#include "parser.h"
+#include "queue.h" // Queue
+#include "output.h" // TemplateOutput
+
+#include <stdint.h> // uint32_t
+#include <sys/types.h> // size_t
+
+namespace hogan {
+
+class Code;
+class Template;
+class TemplateCode;
+
+typedef size_t (*TemplateFunction)(void* obj,
+ TemplateOutput* out,
+ Template* tpl);
+
+class Compiler {
+ public:
+ static Template* Compile(AstNode* ast, Options* options, const char* source);
+};
+
+class Code {
+ public:
+ Code();
+ ~Code();
+
+ void Grow();
+ void Commit();
+
+ uint32_t size;
+ uint32_t page_size;
+
+ char* code;
+ char* guard;
+ bool committed;
+
+ Queue<char*>* data;
+};
+
+class TemplateCode : public Code {
+ public:
+ TemplateCode() {
+ }
+
+ ~TemplateCode() {
+ delete source;
+ }
+
+ inline TemplateFunction AsFunction() {
+ return reinterpret_cast<TemplateFunction>(this->code);
+ }
+
+ // Just for reference
+ const char* source;
+};
+
+
+} // namespace hogan
+
+#endif // _SRC_ASM_H_
49 deps/hogan.jit/src/hogan.cc
@@ -0,0 +1,49 @@
+#include "hogan.h"
+#include "compiler.h"
+#include "parser.h"
+
+#include <stdlib.h> // NULL
+#include <string.h> // strlen
+
+namespace hogan {
+
+Options::Options() {
+ getString = NULL;
+ getObject = NULL;
+ at = NULL;
+ isArray = NULL;
+ getPartial = NULL;
+}
+
+Options::Options(PropertyCallback getString_,
+ PropertyCallback getObject_,
+ NumericPropertyCallback at_,
+ IsArrayCallback isArray_,
+ PartialCallback getPartial_) {
+ getString = getString_;
+ getObject = getObject_;
+ at = at_;
+ isArray = isArray_;
+ getPartial = getPartial_;
+}
+
+Hogan::Hogan(Options* options) {
+ options_ = new Options(*options);
+}
+
+Hogan::~Hogan() {
+ delete options_;
+}
+
+Template* Hogan::Compile(const char* source) {
+ uint32_t len = strlen(source);
+ char* source_ = new char[len];
+ memcpy(source_, source, len);
+
+ Parser parser(source_, len);
+ parser.Parse();
+
+ return Compiler::Compile(parser.Result(), options_, source_);
+}
+
+} // namespace hogan
150 deps/hogan.jit/src/ia32/assembler-ia32.cc
@@ -0,0 +1,150 @@
+#include "assembler.h"
+#include "assembler-ia32.h"
+
+#include <stdint.h> // uintXX_t
+
+namespace hogan {
+
+void Assembler::Push(int reg) {
+ emit(0x50 | reg);
+}
+
+
+void Assembler::PushImm(uint32_t imm) {
+ emit(0x68);
+ Immediate(imm);
+}
+
+
+void Assembler::Pop(int reg) {
+ emit(0x58 | reg);
+}
+
+
+void Assembler::Mov(int dst, int src) {
+ emit(0x8b); // mov
+ emit(0xc0 | dst << 3 | src);
+}
+
+
+void Assembler::MovToContext(uint8_t offset, int src) {
+ emit(0x89); // mov [ebp+offset], src
+ emit(0x45 | src << 3); // modrm
+ Immediate(offset);
+}
+
+
+void Assembler::MovFromContext(int dst, uint8_t offset) {
+ emit(0x8b); // mov dst, [ebp+offset]
+ emit(0x45 | dst << 3); // modrm
+ Immediate(offset);
+}
+
+
+void Assembler::MovImm(int dst, uint64_t imm) {
+ emit(0xb8 | dst); // mov
+
+ Immediate(static_cast<uint32_t>(imm));
+}
+
+
+void Assembler::AddImm(int dst, uint8_t imm) {
+ if (imm == 0) return;
+
+ emit(0x83);
+ emit(0xc0 | dst);
+ Immediate(imm);
+}
+
+
+void Assembler::AddImmToContext(int offset, uint32_t imm) {
+ emit(0x81);
+ emit(0x45); // modrm
+ Immediate(static_cast<uint8_t>(offset));
+ Immediate(imm);
+}
+
+
+void Assembler::AddToContext(int offset, int src) {
+ emit(0x01);
+ emit(0x45 | src << 3); // modrm
+ Immediate(static_cast<uint8_t>(offset));
+}
+
+
+void Assembler::SubImm(int dst, uint8_t imm) {
+ if (imm == 0) return;
+
+ emit(0x83);
+ emit(0xc0 | 0x05 << 3 | dst);
+ Immediate(imm);
+}
+
+
+void Assembler::Inc(int dst) {
+ emit(0xff); // xor
+ emit(0xc0 | dst << 3);
+}
+
+
+void Assembler::Xor(int dst, int src) {
+ emit(0x33); // xor
+ emit(0xc0 | dst << 3 | src);
+}
+
+
+int Assembler::PreCall(int offset, int args) {
+ int delta = 16 - (offset + args * 4) % 16;
+
+ if (delta == 16) return args * 4;
+ SubImm(esp, delta);
+
+ return delta + args * 4;
+}
+
+
+void Assembler::Call(const void* addr) {
+ MovImm(ecx, reinterpret_cast<const uint64_t>(addr));
+ emit(0xff); // Call
+ emit(0xc0 | 2 << 3 | ecx);
+}
+
+
+void Assembler::Leave() {
+ emit(0xc9);
+}
+
+
+void Assembler::Return(uint16_t bytes) {
+ if (bytes == 0) {
+ emit(0xc3);
+ } else {
+ emit(0xc2);
+ Immediate(bytes);
+ }
+}
+
+
+void Assembler::Cmp(int src, uint32_t imm) {
+ if (src == eax) {
+ emit(0x3d);
+ Immediate(imm);
+ } else {
+ assert(false && "Not implemented yet!");
+ }
+}
+
+
+void Assembler::Je(Label* lbl) {
+ emit(0x0f);
+ emit(0x84);
+ Offset(lbl, 4);
+}
+
+
+void Assembler::Jmp(Label* lbl) {
+ emit(0xe9);
+ Offset(lbl, 4);
+}
+
+} // namespace hogan
19 deps/hogan.jit/src/ia32/assembler-ia32.h
@@ -0,0 +1,19 @@
+#ifndef _SRC_ASSEMBLER_IA32_H_
+#define _SRC_ASSEMBLER_IA32_H_
+
+#include "assembler.h"
+
+namespace hogan {
+
+const int eax = 0;
+const int ecx = 1;
+const int edx = 2;
+const int ebx = 3;
+const int esp = 4;
+const int ebp = 5;
+const int esi = 6;
+const int edi = 7;
+
+} // namespace hogan
+
+#endif // _SRC_ASSEMBLER_IA32_H_
297 deps/hogan.jit/src/ia32/codegen-ia32.cc
@@ -0,0 +1,297 @@
+#include "codegen.h"
+#include "assembler-ia32.h"
+#include "assembler.h"
+#include "parser.h" // AstNode
+#include "output.h" // TemplateOutput
+#include "hogan.h" // Options
+
+#include <assert.h> // assert
+#include <stdlib.h> // NULL
+
+namespace hogan {
+
+void Codegen::GeneratePrologue() {
+ Push(ebp);
+ Mov(ebp, esp);
+
+ // Reserve space for 4 pointers
+ SubImm(esp, 16 + 8);
+
+ MovFromContext(eax, 8); // get `obj`
+ MovToContext(-8, eax); // store `obj`
+ MovFromContext(eax, 12); // get `out`
+ MovToContext(-12, eax); // store `out`
+ MovFromContext(eax, 16); // get `template`
+ MovToContext(-16, eax); // store `template`
+
+ Xor(eax, eax); // nullify return value
+ MovToContext(-4, eax);
+}
+
+
+void Codegen::GenerateBlock(AstNode* node) {
+ AstNode* descendant;
+
+ while ((descendant = node->Shift()) != NULL) {
+ switch (descendant->type) {
+ case AstNode::kString:
+ GenerateString(descendant);
+ break;
+ case AstNode::kProp:
+ GenerateProp(descendant, true);
+ break;
+ case AstNode::kRawProp:
+ GenerateProp(descendant, false);
+ break;
+ case AstNode::kIf:
+ GenerateIf(descendant);
+ break;
+ case AstNode::kPartial:
+ GeneratePartial(descendant);
+ break;
+ default:
+ assert(false && "Unexpected");
+ }
+
+ delete descendant;
+ }
+
+ delete node;
+}
+
+
+void Codegen::GenerateEpilogue() {
+ MovFromContext(eax, -4);
+ Mov(esp, ebp);
+ Pop(ebp);
+ Return(0);
+}
+
+
+void Codegen::GenerateString(AstNode* node) {
+ PushCallback method = &TemplateOutput::Push;
+
+ char* value = ToData(node);
+
+ int align = PreCall(0, 4);
+
+ MovFromContext(eax, -12);
+ PushImm(TemplateOutput::kNone);
+ PushImm(node->length); // length
+ PushImm(reinterpret_cast<const uint64_t>(value)); // str to push
+ Push(eax); // out
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(esp, align);
+
+ AddImmToContext(-4, node->length);
+}
+
+
+typedef size_t (*StrLenType)(const char*);
+
+
+void Codegen::GenerateProp(AstNode* node, bool escape) {
+ {
+ PropertyCallback method = options->getString;
+
+ char* value = ToData(node);
+
+ int align = PreCall(0, 2);
+
+ MovFromContext(eax, -8);
+ PushImm(reinterpret_cast<const uint64_t>(value)); // str to push
+ Push(eax); // obj
+ Call(*reinterpret_cast<void**>(&method));
+
+ // Unwind stack and unalign
+ AddImm(esp, align);
+ }
+
+ Label skipPush;
+ Cmp(eax, 0);
+ Je(&skipPush);
+
+ {
+ PushCallback method = &TemplateOutput::Push;
+
+ int align = PreCall(4, 4);
+
+ PushImm((escape ? TemplateOutput::kEscape : TemplateOutput::kNone) |
+ TemplateOutput::kAllocated);
+ PushImm(0); // length
+ Push(eax); // value
+ MovFromContext(eax, -12);
+ Push(eax); // out
+ Call(*reinterpret_cast<void**>(&method)); // push
+
+ AddImm(esp, align); // unalign stack
+ }
+
+ Bind(&skipPush);
+}
+
+
+void Codegen::GenerateIf(AstNode* node) {
+ AstNode* main_block = node->Shift();
+ AstNode* else_block = node->Shift();
+
+ MovFromContext(eax, -8); // save obj
+ Push(eax);
+
+ {
+ PropertyCallback method = options->getObject;
+
+ char* value = ToData(node);
+
+ int delta = PreCall(4, 2);
+
+ PushImm(reinterpret_cast<const uint64_t>(value)); // prop value
+ Push(eax); // obj
+ Call(*reinterpret_cast<void**>(&method));
+
+ // Unalign stack
+ AddImm(esp, delta);
+
+ MovToContext(-8, eax); // Replace context var
+ }
+
+ Label Start, Else, EndIf;
+
+ // Check if object has that prop
+ Cmp(eax, 0);
+ Je(&Else);
+
+ // Push property (needed to restore after iteration loop)
+ Push(eax);
+
+ // Check if we need to iterate props
+ {
+ IsArrayCallback method = options->isArray;
+
+ int delta = PreCall(8, 1);
+
+ Push(eax); // prop value
+ Call(*reinterpret_cast<void**>(&method));
+
+ // Unalign stack
+ AddImm(esp, delta);
+ }
+
+ PushImm(0);
+
+ // If not array - skip to if's body
+ Cmp(eax, 0);
+ Je(&Start);
+
+ // Start of loop
+ Label Iterate, EndIterate;
+ Bind(&Iterate);
+
+ // Get item at index
+ {
+ NumericPropertyCallback method = options->at;
+
+ Pop(eax);
+ Pop(ecx);
+
+ Mov(edx, eax);
+ Inc(eax);
+
+ Push(ecx);
+ Push(eax);
+
+ int delta = PreCall(12, 2);
+
+ Push(edx); // index
+ Push(ecx); // obj
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(esp, delta);
+
+ // If At() returns NULL - we reached end of array
+ Cmp(eax, 0);
+ Je(&EndIterate);
+
+ // Replace context var
+ MovToContext(-8, eax);
+ }
+
+ Bind(&Start);
+
+ int delta = PreCall(12, 0);
+ GenerateBlock(main_block);
+ AddImm(esp, delta);
+
+ Pop(eax);
+
+ Pop(ecx);
+
+ // Check if we reached end of loop
+ Cmp(eax, 0);
+ Je(&EndIf);
+
+ // Store parent and loop index
+ Push(ecx);
+ Push(eax);
+
+ // And continue iterating
+ Jmp(&Iterate);
+
+ Bind(&Else);
+
+ if (else_block != NULL) GenerateBlock(else_block);
+ Jmp(&EndIf);
+
+ Bind(&EndIterate);
+
+ Pop(eax);
+ Pop(ecx);
+
+ Bind(&EndIf);
+
+ Pop(eax);
+ MovToContext(-8, eax); // restore obj
+}
+
+
+void Codegen::GeneratePartial(AstNode* node) {
+ // Get partial
+ {
+ PartialCallback method = options->getPartial;
+
+ char* value = ToData(node);
+
+ int delta = PreCall(0, 2);
+
+ PushImm(reinterpret_cast<const uint64_t>(value)); // partial name
+ MovFromContext(eax, 16); // template
+ Push(eax);
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(esp, delta);
+ }
+
+ Label skipPush;
+ Cmp(eax, 0);
+ Je(&skipPush);
+
+ // Invoke partial
+ {
+ InvokePartialType method = InvokePartial;
+ int delta = PreCall(0, 3);
+
+ MovFromContext(ecx, -12); // out
+ Push(ecx);
+ MovFromContext(ecx, -8); // obj
+ Push(ecx);
+ Push(eax); // partial
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(esp, delta);
+ }
+
+ Bind(&skipPush);
+}
+
+} // namespace hogan
151 deps/hogan.jit/src/lexer.h
@@ -0,0 +1,151 @@
+#ifndef _SRC_LEXER_H_
+#define _SRC_LEXER_H_
+
+#include <stdint.h> // uint32_t
+#include <stdlib.h> // NULL
+
+namespace hogan {
+
+class Lexer {
+ public:
+ class Token {
+ public:
+ enum TokenType {
+ kString,
+ kProp,
+ kRawProp,
+ kRawTaggedProp,
+ kIf,
+ kElse,
+ kEndIf,
+ kPartial,
+ kComment,
+ kEnd
+ };
+
+ Token(TokenType type_) : type(type_), value(NULL), length(0) {
+ }
+
+ Token(TokenType type_, const char* value_, uint32_t length_) : type(type_) {
+ value = value_;
+ length = length_;
+
+ // Trim non-string tokens
+ if (type != kString) {
+ // Remove spaces from start
+ while (value[0] == ' ' && length > 0) {
+ value++;
+ length--;
+ }
+ // And from end
+ while (value[length - 1] == ' ' && length > 0) {
+ length--;
+ }
+ }
+ }
+ ~Token() {}
+
+ TokenType type;
+ const char* value;
+ uint32_t length;
+ };
+
+ Lexer(const char* source_, uint32_t length_) : source(source_),
+ offset(0),
+ length(length_) {
+ }
+ ~Lexer() {}
+
+ Token* Consume() {
+ if (offset == length) return new Token(Token::kEnd);
+
+ // Consume string
+ if (source[offset] != '{' ||
+ offset + 1 >= length ||
+ source[offset + 1] != '{') {
+
+ uint32_t start = offset++;
+ while (offset < length) {
+ if (offset + 1 < length &&
+ source[offset] == '{' &&
+ source[offset + 1] == '{') {
+ break;
+ }
+ offset++;
+ }
+ return new Token(Token::kString, source + start, offset - start);
+ }
+
+ // Skip '{{'
+ offset += 2;
+ if (offset == length) return new Token(Token::kEnd);
+
+ // Handle modificators: '#', '^', '/'
+ Token::TokenType type;
+ do {
+ if (source[offset] == '#') {
+ type = Token::kIf;
+ } else if (source[offset] == '^') {
+ type = Token::kElse;
+ } else if (source[offset] == '/') {
+ type = Token::kEndIf;
+ } else if (source[offset] == '>') {
+ type = Token::kPartial;
+ } else if (source[offset] == '!') {
+ type = Token::kComment;
+ } else if (source[offset] == '{') {
+ type = Token::kRawProp;
+ } else if (source[offset] == '&') {
+ type = Token::kRawTaggedProp;
+ } else {
+ type = Token::kProp;
+ break;
+ }
+ offset++;
+ } while(0);
+
+ // Parse until '}}'
+ uint32_t start = offset;
+ while (offset + 2 < length &&
+ (source[offset + 1] != '}' || source[offset + 2] != '}')) {
+ offset++;
+ }
+
+ // '{{...' without '}}' or '{{}}'
+ if (offset + 2 >= length) return new Token(Token::kEnd);
+ offset++;
+
+ // '}}}' for kRawProp
+ if (type == Token::kRawProp &&
+ (offset + 2 >= length || source[offset + 2] != '}')) {
+ return new Token(Token::kEnd);
+ }
+
+ // kRawTaggedProp is essentially the same as kRawProp
+ Token* prop;
+ if (type != Token::kRawTaggedProp) {
+ prop = new Token(type, source + start, offset - start);
+ } else {
+ prop = new Token(Token::kRawProp, source + start, offset - start);
+ }
+
+ if (type != Token::kRawProp) {
+ // Skip '}}'
+ offset += 2;
+ } else {
+ // Skip '}}}' for kRawProp
+ offset += 3;
+ }
+
+ return prop;
+ }
+
+ private:
+ const char* source;
+ uint32_t offset;
+ uint32_t length;
+};
+
+} // namespace hogan
+
+#endif // _SRC_LEXER_H_
183 deps/hogan.jit/src/output.h
@@ -0,0 +1,183 @@
+#ifndef _SRC_OUTPUT_H_
+#define _SRC_OUTPUT_H_
+
+#include <assert.h> // assert
+#include <sys/types.h> // size_t
+#include <string.h> // strlen, memcpy
+#include <stdio.h> // strlen, memcpy
+
+namespace hogan {
+
+const int initialCapacity = 128;
+
+class TemplateOutput {
+ public:
+ enum ValueFlags {
+ kNone = 0,
+ kEscape = 1,
+ kAllocated = 2
+ };
+
+ TemplateOutput() {
+ chunks = new size_t[initialCapacity << 2];
+ capacity = initialCapacity;
+ total = 0;
+ items = 0;
+ }
+
+ ~TemplateOutput() {
+ delete chunks;
+ }
+
+ inline const char* Escape(const char* value,
+ size_t length,
+ size_t* escaped_length) {
+ size_t i;
+
+ // Fast case: no symbols to escape, just return NULL
+ for (i = 0; i < length; i++) {
+ if (value[i] == '&' || value[i] == '<' || value[i] == '>' ||
+ value[i] == '"' || value[i] == '\'') {
+ break;
+ }
+ }
+ if (i == length) return NULL;
+
+
+ // Slow case: create new string and insert escaped symbols in it
+ size_t newlen = length << 1;
+ char* escaped = new char[newlen];
+
+ size_t offset = 0;
+ for (i = 0; i < length; i++) {
+ // &quot; - is 6 bytes in length
+ // be sure that we have enough space to encode it
+ if (offset + 6 >= newlen) {
+ // If not - double size of buffer
+ char* tmp = new char[newlen << 1];
+ memcpy(tmp, escaped, offset);
+ newlen = newlen << 1;
+ delete escaped;
+ escaped = tmp;
+ }
+
+ switch(value[i]) {
+ case '&':
+ escaped[offset] = '&';
+ escaped[offset + 1] = 'a';
+ escaped[offset + 2] = 'm';
+ escaped[offset + 3] = 'p';
+ escaped[offset + 4] = ';';
+ offset += 5;
+ break;
+ case '<':
+ escaped[offset] = '&';
+ escaped[offset + 1] = 'l';
+ escaped[offset + 2] = 't';
+ escaped[offset + 3] = ';';
+ offset += 4;
+ break;
+ case '>':
+ escaped[offset] = '&';
+ escaped[offset + 1] = 'g';
+ escaped[offset + 2] = 't';
+ escaped[offset + 3] = ';';
+ offset += 4;
+ break;
+ case '"':
+ escaped[offset] = '&';
+ escaped[offset + 1] = 'q';
+ escaped[offset + 2] = 'u';
+ escaped[offset + 3] = 'o';
+ escaped[offset + 4] = 't';
+ escaped[offset + 5] = ';';
+ offset += 6;
+ break;
+ case '\'':
+ escaped[offset] = '&';
+ escaped[offset + 1] = 'a';
+ escaped[offset + 2] = 'p';
+ escaped[offset + 3] = 'o';
+ escaped[offset + 4] = 's';
+ escaped[offset + 5] = ';';
+ offset += 6;
+ break;
+ default:
+ escaped[offset] = value[i];
+ offset++;
+ }
+ }
+
+ *escaped_length = offset;
+ return escaped;
+ }
+
+ inline void Realloc() {
+ if (capacity != 0) return;
+
+ // Reallocate chunks
+ size_t* new_chunks = new size_t[items << 1];
+ memcpy(new_chunks, chunks, items * sizeof(*chunks));
+ delete chunks;
+ chunks = new_chunks;
+
+ // Increase capactiy and size
+ capacity += items >> 2;
+ }
+
+ void Push(const char* chunk, const size_t size, const size_t flags) {
+ const char* value = chunk;
+ size_t size_ = size == 0 ? strlen(chunk) : size;
+ size_t flags_ = flags;
+
+ if ((flags_ & kEscape) == kEscape) {
+ const char* tmp;
+ tmp = Escape(value, size_, &size_);
+ if (tmp != NULL) {
+ value = tmp;
+ flags_ |= kAllocated;
+ }
+ }
+
+ chunks[items] = reinterpret_cast<const size_t>(value);
+ chunks[items + 1] = size_;
+ chunks[items + 2] = flags_;
+
+ items += 4;
+ capacity--;
+ total += size_;
+ Realloc();
+ }
+
+ inline char* Join() {
+ char* result = new char[total + 1];
+ result[total] = 0;
+
+ off_t offset = 0;
+ for (size_t i = 0; i < items; i += 4) {
+ char* value = reinterpret_cast<char*>(chunks[i]);
+ size_t size = chunks[i + 1];
+ size_t flags = chunks[i + 2];
+
+ memcpy(result + offset, value, size);
+ offset += size;
+
+ if ((flags & kAllocated) == kAllocated) {
+ delete value;
+ }
+ }
+ assert(static_cast<size_t>(offset) == total);
+
+ return result;
+ }
+
+ private:
+ size_t* chunks;
+ size_t capacity; // how many item we can insert
+ size_t items; // count of items
+ size_t total; // total byte (sum of chunks' lengths)
+};
+
+} // namespace hogan
+
+#endif // _SRC_OUTPUT_H_
52 deps/hogan.jit/src/parser.cc
@@ -0,0 +1,52 @@
+#include "parser.h"
+
+#include <assert.h> // assert
+
+namespace hogan {
+
+void Parser::Parse() {
+ Lexer::Token* tok = Consume();
+
+ while (tok->type != Lexer::Token::kEnd) {
+ switch (tok->type) {
+ case Lexer::Token::kString:
+ Insert(AstNode::kString, tok);
+ break;
+ case Lexer::Token::kProp:
+ Insert(AstNode::kProp, tok);
+ break;
+ case Lexer::Token::kRawProp:
+ Insert(AstNode::kRawProp, tok);
+ break;
+ case Lexer::Token::kPartial:
+ Insert(AstNode::kPartial, tok);
+ break;
+ case Lexer::Token::kIf:
+ Enter(AstNode::kIf);
+ SetValue(tok);
+ Enter(AstNode::kBlock);
+ break;
+ case Lexer::Token::kElse:
+ Leave(AstNode::kBlock);
+ Enter(AstNode::kBlock);
+ SetValue(tok);
+ break;
+ case Lexer::Token::kEndIf:
+ Leave(AstNode::kBlock);
+ Leave(AstNode::kIf);
+ break;
+ case Lexer::Token::kComment:
+ break;
+ default:
+ assert(false && "Unexpected lexer token");
+ }
+
+ delete tok;
+ tok = Consume();
+ }
+
+ // Delete kEnd token too
+ delete tok;
+}
+
+} // namespace hogan
102 deps/hogan.jit/src/parser.h
@@ -0,0 +1,102 @@
+#ifndef _SRC_PARSER_H_
+#define _SRC_PARSER_H_
+
+#include "lexer.h"
+#include "queue.h"
+#include <assert.h> // assert
+#include <stdlib.h> // NULL
+
+namespace hogan {
+
+class AstNode {
+ public:
+ enum AstNodeType {
+ kBlock,
+ kString,
+ kProp,
+ kRawProp,
+ kIf,
+ kPartial
+ };
+
+ AstNode(AstNodeType type_) : type(type_),
+ value(NULL),
+ length(0),
+ ascendant(NULL) {
+ }
+ ~AstNode() {
+ ascendant = NULL;
+ }
+
+ inline void Push(AstNode* node) {
+ descendants.Push(node);
+ }
+
+ inline AstNode* Shift() {
+ return descendants.Shift();
+ }
+
+ void SetAscendant(AstNode* node) {
+ assert(ascendant == NULL);
+ ascendant = node;
+ }
+
+ AstNode* GetAscendant() {
+ return ascendant;
+ }
+
+ AstNodeType type;
+ const void* value;
+ uint32_t length;
+
+ private:
+ Queue<AstNode*> descendants;
+ AstNode* ascendant;
+};
+
+
+class Parser : Lexer {
+ public:
+ Parser(const char* source, uint32_t length) : Lexer(source, length) {
+ ast = new AstNode(AstNode::kBlock);
+ current = ast;
+ }
+ ~Parser() {}
+
+ void Parse();
+
+ AstNode* Result() {
+ return ast;
+ }
+
+ void Enter(AstNode::AstNodeType type) {
+ AstNode* node = new AstNode(type);
+ node->SetAscendant(current);
+ current->Push(node);
+ current = node;
+ }
+
+ void SetValue(const Lexer::Token* token) {
+ current->value = token->value;
+ current->length = token->length;
+ }
+
+ void Leave(AstNode::AstNodeType type) {
+ assert(current->type == type);
+ current = current->GetAscendant();
+ }
+
+ void Insert(AstNode::AstNodeType type, const Lexer::Token* token) {
+ Enter(type);
+ SetValue(token);
+ Leave(type);
+ }
+
+ private:
+ AstNode* ast;
+ AstNode* current;
+};
+
+} // namespace hogan
+
+#endif // _SRC_PARSER_H_
61 deps/hogan.jit/src/queue.h
@@ -0,0 +1,61 @@
+#ifndef _SRC_QUEUE_H_
+#define _SRC_QUEUE_H_
+
+#include <stdlib.h> // NULL
+
+namespace hogan {
+
+template <class T>
+class Queue {
+ public:
+ class Entity {
+ public:
+ T value;
+ Entity* next;
+
+ Entity(T value_) : value(value_), next(NULL) {
+ }
+
+ ~Entity() {}
+ };
+
+ Queue() : head(NULL), current(NULL) {
+ }
+ ~Queue() {
+ T value;
+ while ((value = Shift()) != NULL) {
+ }
+ }
+
+ void Push(T value) {
+ Entity* next = new Entity(value);
+ if (head == NULL) {
+ head = next;
+ current = head;
+ } else {
+ current->next = next;
+ current = next;
+ }
+ }
+
+ T Shift() {
+ if (head == NULL) return NULL;
+
+ Entity* old = head;
+ T result = old->value;
+ head = old->next;
+
+ delete old;
+
+ return result;
+ }
+
+ private:
+ Entity* head;
+ Entity* current;
+};
+
+
+} // namespace hogan
+
+#endif // _SRC_QUEUE_H_
164 deps/hogan.jit/src/x64/assembler-x64.cc
@@ -0,0 +1,164 @@
+#include "assembler.h"
+#include "assembler-x64.h"
+
+#include <stdint.h> // uintXX_t
+
+namespace hogan {
+
+void Assembler::Push(int reg) {
+ emit(0x50 | reg);
+}
+
+
+void Assembler::PushImm(uint32_t imm) {
+ emit(0x68);
+ Immediate(imm);
+}
+
+
+void Assembler::Pop(int reg) {
+ emit(0x58 | reg);
+}
+
+
+void Assembler::Mov(int dst, int src) {
+ emit(0x48); // REX.W prefix
+ emit(0x8b); // mov
+ emit(0xc0 | dst << 3 | src);
+}
+
+
+void Assembler::MovToContext(uint8_t offset, int src) {
+ emit(0x48); // REX.W prefix
+ emit(0x89); // mov [ebp+offset], src
+ emit(0x45 | src << 3); // modrm
+ Immediate(offset);
+}
+
+
+void Assembler::MovFromContext(int dst, uint8_t offset) {
+ emit(0x48);
+ emit(0x8b); // mov dst, [ebp+offset]
+ emit(0x45 | dst << 3); // modrm
+ Immediate(offset);
+}
+
+
+void Assembler::MovImm(int dst, uint64_t imm) {
+ emit(0x48 | (dst >> 3) & 1); // REX.W + modrm extension prefix
+ emit(0xb8 | dst & 7); // mov
+
+ Immediate(imm);
+}
+
+
+void Assembler::AddImm(int dst, uint8_t imm) {
+ if (imm == 0) return;
+
+ emit(0x48); // REX prefix
+ emit(0x83);
+ emit(0xc0 | dst);
+ Immediate(imm);
+}
+
+
+void Assembler::AddImmToContext(int offset, uint32_t imm) {
+ if (imm == 0) return;
+
+ emit(0x48); // REX prefix
+ emit(0x81);
+ emit(0x45); // modrm
+ Immediate(static_cast<uint8_t>(offset));
+ Immediate(imm);
+}
+
+
+void Assembler::AddToContext(int offset, int src) {
+ emit(0x48); // REX prefix
+ emit(0x01);
+ emit(0x45 | src << 3); // modrm
+ Immediate(static_cast<uint8_t>(offset));
+}
+
+
+void Assembler::SubImm(int dst, uint8_t imm) {
+ if (imm == 0) return;
+
+ emit(0x48); // REX prefix
+ emit(0x83);
+ emit(0xc0 | 0x05 << 3 | dst);
+ Immediate(imm);
+}
+
+
+void Assembler::Inc(int dst) {
+ emit(0x48); // REX prefix
+ emit(0xff); // xor
+ emit(0xc0 | dst << 3);
+}
+
+
+void Assembler::Xor(int dst, int src) {
+ emit(0x48); // REX prefix
+ emit(0x33); // xor
+ emit(0xc0 | dst << 3 | src);
+}
+
+
+int Assembler::PreCall(int offset, int args) {
+ int delta = 16 - offset % 16;
+
+ if (delta == 16) return 0;
+ SubImm(rsp, delta);
+
+ return delta;
+}
+
+
+void Assembler::Call(const void* addr) {
+ MovImm(r10, reinterpret_cast<const uint64_t>(addr));
+ emit(0x40 | (r10 >> 3) & 1); // RX + modrm extension
+ emit(0xff); // Call
+ emit(0xc0 | 2 << 3 | r10 & 7);
+}
+
+
+void Assembler::Leave() {
+ emit(0xc9);
+}
+
+
+void Assembler::Return(uint16_t bytes) {
+ if (bytes == 0) {
+ emit(0xc3);
+ } else {
+ emit(0xc2);
+ Immediate(bytes);
+ }
+}
+
+
+void Assembler::Cmp(int src, uint32_t imm) {
+ emit(0x48); // REX prefix
+ if (src == rax) {
+ emit(0x3d);
+ Immediate(imm);
+ } else {
+ assert(false && "Not implemented yet!");
+ }
+}
+
+
+void Assembler::Je(Label* lbl) {
+ emit(0x0f);
+ emit(0x84);
+ Offset(lbl, 4);
+}
+
+
+void Assembler::Jmp(Label* lbl) {
+ emit(0xe9);
+ Offset(lbl, 4);
+}
+
+} // namespace hogan
27 deps/hogan.jit/src/x64/assembler-x64.h
@@ -0,0 +1,27 @@
+#ifndef _SRC_ASSEMBLER_X64_H_
+#define _SRC_ASSEMBLER_X64_H_
+
+#include "assembler.h"
+
+namespace hogan {
+
+const int rax = 0;
+const int rcx = 1;
+const int rdx = 2;
+const int rbx = 3;
+const int rsp = 4;
+const int rbp = 5;
+const int rsi = 6;
+const int rdi = 7;
+const int r8 = 8;
+const int r9 = 9;
+const int r10 = 10;
+const int r11 = 11;
+const int r12 = 12;
+const int r13 = 13;
+const int r14 = 14;
+const int r15 = 15;
+
+} // namespace hogan
+
+#endif // _SRC_ASSEMBLER_X64_H_
277 deps/hogan.jit/src/x64/codegen-x64.cc
@@ -0,0 +1,277 @@
+#include "codegen.h"
+#include "assembler-x64.h"
+#include "assembler.h"
+#include "parser.h" // AstNode
+#include "output.h" // TemplateOutput
+#include "hogan.h" // Options
+
+#include <assert.h> // assert
+#include <stdlib.h> // NULL
+
+namespace hogan {
+
+void Codegen::GeneratePrologue() {
+ Push(rbp);
+ Mov(rbp, rsp);
+
+ // Reserve space for 4 pointers
+ // and align stack
+ SubImm(rsp, 32);
+
+ MovToContext(-32, rcx); // store `template`
+ MovToContext(-24, rsi); // store `out`
+ MovToContext(-16, rdi); // store `obj`
+ Xor(rax, rax); // nullify return value
+ MovToContext(-8, rax);
+}
+
+
+void Codegen::GenerateEpilogue() {
+ MovFromContext(rax, -8);
+ Mov(rsp, rbp);
+ Pop(rbp);
+ Return(0);
+}
+
+
+void Codegen::GenerateBlock(AstNode* node) {
+ AstNode* descendant;
+
+ while ((descendant = node->Shift()) != NULL) {
+ switch (descendant->type) {
+ case AstNode::kString:
+ GenerateString(descendant);
+ break;
+ case AstNode::kProp:
+ GenerateProp(descendant, true);
+ break;
+ case AstNode::kRawProp:
+ GenerateProp(descendant, false);
+ break;
+ case AstNode::kIf:
+ GenerateIf(descendant);
+ break;
+ case AstNode::kPartial:
+ GeneratePartial(descendant);
+ break;
+ default:
+ assert(false && "Unexpected");
+ }
+
+ delete descendant;
+ }
+
+ delete node;
+}
+
+
+void Codegen::GenerateString(AstNode* node) {
+ PushCallback method = &TemplateOutput::Push;
+
+ char* value = ToData(node);
+
+ int delta = PreCall(0, 4);
+
+ MovFromContext(rdi, -24); // out
+ MovImm(rsi, reinterpret_cast<const uint64_t>(value)); // value
+ MovImm(rdx, node->length); // length
+ MovImm(rcx, TemplateOutput::kNone); // flags
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+}
+
+
+void Codegen::GenerateProp(AstNode* node, bool escape) {
+ {
+ PropertyCallback method = options->getString;
+
+ char* value = ToData(node);
+
+ int delta = PreCall(0, 2);
+
+ MovFromContext(rdi, -16); // obj
+ MovImm(rsi, reinterpret_cast<const uint64_t>(value)); // get prop value
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+ }
+
+ Label skipPush;
+ Cmp(rax, 0);
+ Je(&skipPush);
+
+ {
+ PushCallback method = &TemplateOutput::Push;
+
+ int delta = PreCall(8, 4);
+
+ MovFromContext(rdi, -24); // out
+ Mov(rsi, rax); // result of get prop
+ MovImm(rdx, 0); // let output stream determine size
+ MovImm(rcx, (escape ? TemplateOutput::kEscape : TemplateOutput::kNone) |
+ TemplateOutput::kAllocated);
+ Call(*reinterpret_cast<void**>(&method)); // push
+
+ AddImm(rsp, delta);
+ }
+
+ Bind(&skipPush);
+}
+
+
+void Codegen::GenerateIf(AstNode* node) {
+ AstNode* main_block = node->Shift();
+ AstNode* else_block = node->Shift();
+
+ MovFromContext(rdi, -16); // save obj
+ Push(rdi);
+
+ {
+ PropertyCallback method = options->getObject;
+
+ char* value = ToData(node);
+
+ int delta = PreCall(8, 2);
+
+ MovImm(rsi, reinterpret_cast<const uint64_t>(value)); // get prop value
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+
+ MovToContext(-16, rax); // Replace context var
+ }
+
+ Label Start, Else, EndIf;
+
+ // Check if object has that prop
+ Cmp(rax, 0);
+ Je(&Else);
+
+ // Push property (needed to restore after iteration loop)
+ Push(rax);
+
+ // Check if we need to iterate props
+ {
+ IsArrayCallback method = options->isArray;
+
+ int delta = PreCall(16, 1);
+
+ Mov(rdi, rax);
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+ }
+
+ PushImm(0);
+
+ // If not array - skip to if's body
+ Cmp(rax, 0);
+ Je(&Start);
+
+ // Start of loop
+ Label Iterate, EndIterate;
+ Bind(&Iterate);
+
+ // Get item at index
+ {
+ NumericPropertyCallback method = options->at;
+
+ Pop(rax);
+ Pop(rdi);
+
+ Mov(rsi, rax);
+ Inc(rax);
+
+ Push(rdi);
+ Push(rax);
+
+ int delta = PreCall(24, 2);
+
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+
+ // If At() returns NULL - we reached end of array
+ Cmp(rax, 0);
+ Je(&EndIterate);
+
+ // Replace context var
+ MovToContext(-16, rax);
+ }
+
+ Bind(&Start);
+
+ int delta = PreCall(24, 0);
+
+ GenerateBlock(main_block);
+
+ AddImm(rsp, delta);
+
+ Pop(rax);
+ Pop(rdi);
+
+ Cmp(rax, 0);
+ Je(&EndIf);
+
+ // Store parent and loop index
+ Push(rdi);
+ Push(rax);
+
+ // And continue iterating
+ Jmp(&Iterate);
+
+ Bind(&Else);
+
+ if (else_block != NULL) GenerateBlock(else_block);
+ Jmp(&EndIf);
+
+ Bind(&EndIterate);
+
+ Pop(rax);
+ Pop(rdi);
+
+ Bind(&EndIf);
+
+ Pop(rdi);
+ MovToContext(-16, rdi); // restore obj
+}
+
+
+void Codegen::GeneratePartial(AstNode* node) {
+ // Get partial
+ {
+ PartialCallback method = options->getPartial;
+
+ char* value = ToData(node);
+
+ int delta = PreCall(0, 1);
+
+ MovFromContext(rdi, -24); // template
+ MovImm(rsi, reinterpret_cast<const uint64_t>(value)); // partial name
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+ }
+
+ Label skipPush;
+ Cmp(rax, 0);
+ Je(&skipPush);
+
+ // Invoke partial
+ {
+ InvokePartialType method = InvokePartial;
+ int delta = PreCall(0, 3);
+
+ Mov(rdi, rax);
+ MovFromContext(rsi, -16); // obj
+ MovFromContext(rdx, -24); // out
+ Call(*reinterpret_cast<void**>(&method));
+
+ AddImm(rsp, delta);
+ }
+
+ Bind(&skipPush);
+}
+
+} // namespace hogan
71 deps/hogan.jit/test/bench-basic.cc
@@ -0,0 +1,71 @@
+#include "test.h"
+
+static bool html = false;
+static char* adjective;
+
+class Object {
+ public:
+ static const void* GetString(void* obj, const char* key) {
+ adjective = new char[100];
+ if (!html) {
+ memcpy(adjective, "neat", 5);
+ } else {
+ memcpy(adjective, "<b>adjective</b>", 17);
+ }
+ return reinterpret_cast<const void*>(adjective);
+ }
+};
+
+TEST_START("bench basic")
+ Options options(Object::GetString,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ Hogan hogan(&options);
+
+ Object data;
+ Template* t;
+ char* out;
+
+ const int cnum = 200000;
+
+ BENCH_START(compile, cnum)
+ for (int i = 0; i < cnum; i++) {
+ t = hogan.Compile("some {{adjective}} template.");
+ delete t;
+ }
+ BENCH_END(compile, cnum)
+
+ const int num = 12000000;
+
+ t = hogan.Compile("some {{{adjective}}} template.");
+ BENCH_START(unescaped, num)
+ for (int i = 0; i < num; i++) {
+ out = t->Render(&data);
+ delete out;
+ }
+ BENCH_END(unescaped, num)
+ delete t;
+
+ t = hogan.Compile("some {{adjective}} template.");
+ BENCH_START(escaped, num)
+ for (int i = 0; i < num; i++) {
+ out = t->Render(&data);
+ delete out;
+ }
+ BENCH_END(escaped, num)
+ delete t;
+
+ html = true;
+
+ t = hogan.Compile("some {{adjective}} template.");
+ BENCH_START(escaped_html, num)
+ for (int i = 0; i < num; i++) {
+ out = t->Render(&data);
+ delete out;
+ }
+ BENCH_END(escaped_html, num)
+ delete t;
+
+TEST_END("bench basic")
118 deps/hogan.jit/test/test-api.cc
@@ -0,0 +1,118 @@
+#include "test.h"
+
+static const char* arrRef = "neat";
+
+class Object {
+ public:
+ static const void* GetString(void* obj, const char* key) {
+ char* value = new char[10];
+ if (strcmp(key, "adjective") == 0) {
+ memcpy(value, "neat", 5);
+ return value;
+ } else if (strcmp(key, "html") == 0) {
+ memcpy(value, "&<>'\"", 6);
+ return value;
+ } else {
+ assert(0 && "unexpected");
+ return NULL;
+ }
+ }
+ static const void* GetObject(void* obj, const char* key) {
+ if (strcmp(key, "prop") == 0) {
+ return obj;
+ } else if (strcmp(key, "arrprop") == 0) {
+ return reinterpret_cast<const void*>(arrRef);
+ } else {
+ return NULL;
+ }
+ }
+
+ static const void* At(void* obj, const int index) {
+ return index < 3 ? obj : NULL;
+ }
+
+ static int IsArray(void* obj) {
+ return obj == reinterpret_cast<const void*>(arrRef);
+ }
+};
+
+TEST_START("API test")
+ Options options(Object::GetString,
+ Object::GetObject,
+ Object::At,
+ Object::IsArray,
+ NULL);
+ Hogan hogan(&options);
+
+ Object data;
+ Template* t;
+ char* out;
+
+ t = hogan.Compile("some {{adjective}} template. "
+ "{{#prop}}yeah{{^prop}}oh noes{{/prop}}");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("some neat template. yeah", out) == 0);
+
+ delete t;
+ delete out;
+
+ t = hogan.Compile("some {{adjective}} template."
+ "{{#arrprop}} o{{^arrprop}}oh noes{{/arrprop}}");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("some neat template. o o o", out) == 0);
+
+ delete t;
+ delete out;
+
+ t = hogan.Compile("some {{ adjective }} template. "
+ "{{#nprop}}yeah{{^nprop}}oh noes{{/nprop}}");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("some neat template. oh noes", out) == 0);
+
+ delete out;
+ delete t;
+
+ t = hogan.Compile("some template with{{! comments }}.");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("some template with.", out) == 0);
+
+ delete out;
+ delete t;
+
+ t = hogan.Compile("escaped value {{html}}.");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("escaped value &amp;&lt;&gt;&apos;&quot;.", out) == 0);
+
+ delete out;
+ delete t;
+
+ t = hogan.Compile("escaped value {{{html}}}.");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("escaped value &<>'\".", out) == 0);
+
+ delete out;
+ delete t;
+
+ t = hogan.Compile("escaped value {{& html }}.");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strcmp("escaped value &<>'\".", out) == 0);
+
+ delete out;
+ delete t;
+
+
+TEST_END("API test")
40 deps/hogan.jit/test/test-output-realloc.cc
@@ -0,0 +1,40 @@
+#include "test.h"
+
+class Object {
+ public:
+ static const void* GetObject(void* obj, const char* key) {
+ return obj;
+ }
+
+ static const void* At(void* obj, const int index) {
+ return index < 10000 ? obj : NULL;
+ }
+
+ static int IsArray(void* obj) {
+ return true;
+ }
+};
+
+TEST_START("Template output reallocation")
+ Options options(NULL,
+ Object::GetObject,
+ Object::At,
+ Object::IsArray,
+ NULL);
+
+ Hogan hogan(&options);
+
+ Object data;
+ Template* t;
+ char* out;
+
+ t = hogan.Compile("{{#iterate}}string{{/iterate}}");
+
+ out = t->Render(&data);
+ assert(out != NULL);
+ assert(strlen(out) == 6 * 10000);
+
+ delete t;
+ delete out;
+
+TEST_END("Template output reallocation")
32 deps/hogan.jit/test/test-partials.cc
@@ -0,0 +1,32 @@
+#include "test.h"
+
+static Template* partial = NULL;
+
+Template* GetPartial(Template* t, const char* name) {
+ assert(strcmp(name, "partial") == 0);
+ return partial;
+};
+
+TEST_START("Partials")
+ Options options;
+ options.getPartial = GetPartial;
+
+ Hogan hogan(&options);
+
+ void* data = NULL;
+
+ Template* t;
+ char* out;
+
+ partial = hogan.Compile(" partial ");
+ t = hogan.Compile("string{{>partial}}string");
+
+ out = t->Render(data);
+ assert(out != NULL);
+ assert(strcmp("string partial string", out) == 0);
+
+ delete partial;
+ delete t;
+ delete out;
+
+TEST_END("Partials")
47 deps/hogan.jit/test/test.h
@@ -0,0 +1,47 @@
+#ifndef _TEST_TEST_H_
+#define _TEST_TEST_H_
+
+#include "hogan.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+
+using namespace hogan;
+
+#define TEST_START(name)\
+int main(void) {\
+ fprintf(stdout, "-- %s --\n", name);
+
+#define TEST_END(name)\
+ fclose(stdout);\
+ return 0;\
+}
+
+#define BENCH_START(name, num)\
+timeval __bench_##name##_start;\
+gettimeofday(&__bench_##name##_start, NULL);
+
+#define BENCH_END(name, num)\
+timeval __bench_##name##_end;\
+gettimeofday(&__bench_##name##_end, NULL);\
+double __bench_##name##_total = __bench_##name##_end.tv_sec -\
+ __bench_##name##_start.tv_sec +\
+ __bench_##name##_end.tv_usec * 1e-6 -\
+ __bench_##name##_start.tv_usec * 1e-6;\
+if ((num) != 0) {\
+ fprintf(stdout, #name " : %f ops/sec\n",\
+ (num) / __bench_##name##_total);\
+} else {\
+ fprintf(stdout, #name " : %fs\n",\
+ __bench_##name##_total);\
+}
+
+#endif // _TEST_TEST_H_
3  lib/hogan.jit.js
@@ -0,0 +1,3 @@
+exports.binding = require('./hogan.jit/hogan.node');
+
+exports.compile = require('./hogan.jit/core').compile;
6 lib/hogan.jit/core.js
@@ -0,0 +1,6 @@
+var core = exports,
+ hogan = require('../hogan.jit');
+
+core.compile = function compile(string) {
+ return new hogan.binding.Template(string);
+};
14 package.json
@@ -0,0 +1,14 @@
+{
+ "name": "hogan.jit",
+ "version": "0.0.1",
+ "author": "Fedor Indutny <fedor@indutny.com>",
+ "main": "./lib/hogan.jit",
+ "dependencies": {
+ },
+ "devDependencies": {
+ "mocha": "0.10.x"
+ },
+ "scripts": {
+ "test": "mocha --ui tdd --growl --reporter spec test/*-test.js"
+ }
+}
103 src/node_hogan.cc
@@ -0,0 +1,103 @@
+#include <node.h>
+#include <v8.h>
+#include <string.h> // memcpy
+#include <hogan.h>
+
+#include "node_hogan.h"
+
+
+using namespace node;
+using namespace v8;
+
+
+#define UNWRAP\
+ Object* o = reinterpret_cast<Object*>(obj);
+
+
+void HoganTemplate::Initialize(Handle<Object> target) {
+ hogan::Options options;
+
+ options.getString = HoganTemplate::GetString;
+ options.getObject = HoganTemplate::GetObject;
+ options.at = HoganTemplate::ObjectAt;
+ options.isArray = HoganTemplate::IsArray;
+
+ api = new hogan::Hogan(&options);
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(HoganTemplate::New);
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+ t->SetClassName(String::NewSymbol("Template"));
+
+ NODE_SET_PROTOTYPE_METHOD(t, "render", HoganTemplate::Render);
+
+ target->Set(String::NewSymbol("Template"), t->GetFunction());
+}
+
+
+const void* HoganTemplate::GetString(void* obj, const char* key) {
+ UNWRAP
+ Handle<Value> value = o->Get(String::NewSymbol(key));
+ if (value->IsUndefined()) return NULL;
+
+ String::Utf8Value val(value->ToString());
+ char* str = new char[val.length()];
+ memcpy(str, *val, val.length());
+
+ return str;
+}
+
+
+const void* HoganTemplate::GetObject(void* obj, const char* key) {
+ UNWRAP
+ Handle<Value> value = o->Get(String::NewSymbol(key));
+ if (value->IsUndefined()) return NULL;
+ if (value->IsFalse()) return NULL;
+
+ return reinterpret_cast<void*>(*value->ToObject());
+}
+
+
+const void* HoganTemplate::ObjectAt(void* obj, const int index) {
+ UNWRAP
+ Local<Array> arr = Array::Cast(o);
+ if (static_cast<uint32_t>(index) >= arr->Length()) return NULL;
+
+ Handle<Value> value = arr->Get(index);
+ if (value->IsUndefined()) return NULL;
+
+ return reinterpret_cast<void*>(*value->ToObject());
+}
+
+
+int HoganTemplate::IsArray(void* obj) {
+ UNWRAP
+
+ return o->IsArray();
+}
+
+
+Handle<Value> HoganTemplate::New(const Arguments& args) {
+ HandleScope scope;
+
+ String::Utf8Value v(args[0].As<String>());
+ HoganTemplate* t = new HoganTemplate(*v);
+
+ t->Wrap(args.Holder());
+
+ return Undefined();
+}
+
+
+Handle<Value> HoganTemplate::Render(const Arguments& args) {
+ HandleScope scope;
+
+ HoganTemplate* t = ObjectWrap::Unwrap<HoganTemplate>(args.This());
+
+ Local<Object> obj = args[0].As<Object>();
+ char* out = t->tpl->Render(reinterpret_cast<void*>(*obj));
+
+ return String::New(out);
+}
+
+
+NODE_MODULE(hogan, HoganTemplate::Initialize);
38 src/node_hogan.h
@@ -0,0 +1,38 @@
+#ifndef _SRC_NODE_HOGAN_H_
+#define _SRC_NODE_HOGAN_H_
+
+#include <node.h>
+#include <v8.h>
+#include <hogan.h>
+
+using namespace node;
+using namespace v8;
+
+
+static hogan::Hogan* api;
+
+class HoganTemplate : public ObjectWrap {
+ public:
+ static void Initialize(Handle<Object> target);
+
+ static const void* GetString(void* obj, const char* key);
+ static const void* GetObject(void* obj, const char* key);
+ static const void* ObjectAt(void* obj, const int index);
+ static int IsArray(void* obj);
+
+ static Handle<Value> New(const Arguments& args);
+ static Handle<Value> Render(const Arguments& args);
+
+ HoganTemplate(char* source) {
+ tpl = api->Compile(source);
+ }
+
+ ~HoganTemplate() {
+ delete tpl;
+ }
+
+ protected:
+ hogan::Template* tpl;
+};
+
+#endif // _SRC_NODE_HOGAN_H_
22 test/api-test.js
@@ -0,0 +1,22 @@
+var hogan = require('../'),
+ assert = require('assert');
+
+suite('API test', function() {
+ test('only string', function() {
+ assert.equal(hogan.compile('just a string').render(), 'just a string');
+ });
+
+ test('prop and string', function() {
+ assert.equal(hogan.compile('just {{a}} string').render({
+ a: 'not a'
+ }), 'just not a string');
+ });
+
+ test('if', function() {
+ var t = hogan.compile('just {{#prop}}a{{^prop}}not a{{/prop}} string');
+
+ assert.equal(t.render({ prop: true }), 'just a string');
+ assert.equal(t.render({ prop: false }), 'just not a string');
+ assert.equal(t.render({ prop: [1, 2, 3] }), 'just aaa string');
+ });
+});
38 wscript
@@ -0,0 +1,38 @@
+import Options
+from os.path import exists
+from shutil import copy2 as copy
+
+TARGET = 'hogan'
+TARGET_FILE = '%s.node' % TARGET
+built = 'build/Release/%s' % TARGET_FILE
+dest = 'lib/hogan.jit/%s' % TARGET_FILE
+
+def set_options(opt):
+ opt.tool_options("compiler_cxx")
+
+def configure(conf):
+ conf.check_tool("compiler_cxx")
+ conf.check_tool("node_addon")
+
+def pre(ctx):
+ if Options.platform == 'darwin' and ctx.env['DEST_CPU'] == 'i386':
+ ctx.exec_command('make -B ARCH=i386 MODE=RELEASE -C ../deps/hogan.jit/')
+ else:
+ ctx.exec_command('make -B ARCH=x64 MODE=RELEASE -C ../deps/hogan.jit/')
+
+def build(bld):
+ bld.add_pre_fun(pre)
+ obj = bld.new_task_gen("cxx", "shlib", "node_addon")
+ obj.cxxflags = ["-g", "-D_LARGEFILE_SOURCE", "-Wall"]
+ obj.ldflags = ["../deps/hogan.jit/hogan.a"]
+ obj.target = TARGET
+ obj.source = "src/node_hogan.cc"
+ obj.includes = "src/ deps/hogan.jit/include"
+
+def shutdown():
+ if Options.commands['clean']:
+ if exists(TARGET_FILE):
+ unlink(TARGET_FILE)
+ else:
+ if exists(built):
+ copy(built, dest)
Please sign in to comment.
Something went wrong with that request. Please try again.