mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-23 11:13:28 +01:00
Add a new example to the LLVM distribution: a trace-based Brainfuck compiler that uses LLVM as its code generator.
llvm-svn: 112465
This commit is contained in:
parent
eddd738031
commit
fd4ce93e17
@ -10,7 +10,8 @@ LEVEL=..
|
||||
|
||||
include $(LEVEL)/Makefile.config
|
||||
|
||||
PARALLEL_DIRS:= BrainF Fibonacci HowToUseJIT Kaleidoscope ModuleMaker
|
||||
PARALLEL_DIRS:= BrainF Fibonacci HowToUseJIT Kaleidoscope ModuleMaker \
|
||||
TracingBrainF
|
||||
|
||||
ifeq ($(HAVE_PTHREAD),1)
|
||||
PARALLEL_DIRS += ParallelJIT
|
||||
|
77
examples/TracingBrainF/BrainF.h
Normal file
77
examples/TracingBrainF/BrainF.h
Normal file
@ -0,0 +1,77 @@
|
||||
//===-- BrainF.h - BrainF compiler class ----------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
#ifndef BRAINF_H
|
||||
#define BRAINF_H
|
||||
|
||||
#include "llvm/LLVMContext.h"
|
||||
#include "llvm/Module.h"
|
||||
#include "llvm/ExecutionEngine/GenericValue.h"
|
||||
#include "llvm/ExecutionEngine/JIT.h"
|
||||
#include "llvm/PassManager.h"
|
||||
#include "llvm/Support/IRBuilder.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
class BrainFTraceRecorder {
|
||||
struct BrainFTraceNode {
|
||||
uint8_t opcode;
|
||||
size_t pc;
|
||||
BrainFTraceNode(uint8_t o, size_t p)
|
||||
: opcode(o), pc(p), left(0), right(0) { }
|
||||
void dump(unsigned level);
|
||||
|
||||
// On an if, left is the x != 0 edge.
|
||||
// A value of 0 indicates an un-traced edge.
|
||||
// A value of ~0ULL indicates an edge to the trace head.
|
||||
BrainFTraceNode *left, *right;
|
||||
};
|
||||
|
||||
uint8_t prev_opcode;
|
||||
uint8_t *iteration_count;
|
||||
std::pair<uint8_t, size_t> *trace_begin, *trace_end, *trace_tail;
|
||||
DenseMap<size_t, BrainFTraceNode*> trace_map;
|
||||
Module *module;
|
||||
BasicBlock *Header;
|
||||
Value *DataPtr;
|
||||
PHINode *HeaderPHI;
|
||||
ExecutionEngine *EE;
|
||||
|
||||
const IntegerType *int_type;
|
||||
const FunctionType *op_type;
|
||||
GlobalValue *bytecode_array, *executed_flag;
|
||||
Value *getchar_func, *putchar_func;
|
||||
FunctionPassManager *FPM;
|
||||
|
||||
|
||||
void commit();
|
||||
void initialize_module();
|
||||
void compile(BrainFTraceNode* trace);
|
||||
void compile_opcode(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_plus(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_minus(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_left(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_right(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_put(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_get(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_if(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
void compile_back(BrainFTraceNode *node, IRBuilder<>& builder);
|
||||
|
||||
public:
|
||||
BrainFTraceRecorder();
|
||||
~BrainFTraceRecorder();
|
||||
|
||||
void record(size_t pc, uint8_t opcode);
|
||||
void record_simple(size_t pc, uint8_t opcode);
|
||||
};
|
||||
|
||||
#endif
|
351
examples/TracingBrainF/BrainFCodeGen.cpp
Normal file
351
examples/TracingBrainF/BrainFCodeGen.cpp
Normal file
@ -0,0 +1,351 @@
|
||||
//===-- BrainFCodeGen.cpp - BrainF trace compiler -----------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
#include "BrainF.h"
|
||||
#include "BrainFVM.h"
|
||||
#include "llvm/Attributes.h"
|
||||
#include "llvm/Support/StandardPasses.h"
|
||||
#include "llvm/Target/TargetData.h"
|
||||
#include "llvm/Target/TargetSelect.h"
|
||||
#include "llvm/Transforms/Scalar.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
|
||||
/// initialize_module - perform setup of the LLVM code generation system.
|
||||
void BrainFTraceRecorder::initialize_module() {
|
||||
LLVMContext &Context = module->getContext();
|
||||
|
||||
// Initialize the code generator, and enable aggressive code generation.
|
||||
InitializeNativeTarget();
|
||||
EngineBuilder builder(module);
|
||||
builder.setOptLevel(CodeGenOpt::Aggressive);
|
||||
EE = builder.create();
|
||||
|
||||
// Create a FunctionPassManager to handle running optimization passes
|
||||
// on our generated code. Setup a basic suite of optimizations for it.
|
||||
FPM = new llvm::FunctionPassManager(module);
|
||||
FPM->add(createInstructionCombiningPass());
|
||||
FPM->add(createCFGSimplificationPass());
|
||||
FPM->add(createScalarReplAggregatesPass());
|
||||
FPM->add(createSimplifyLibCallsPass());
|
||||
FPM->add(createInstructionCombiningPass());
|
||||
FPM->add(createJumpThreadingPass());
|
||||
FPM->add(createCFGSimplificationPass());
|
||||
FPM->add(createInstructionCombiningPass());
|
||||
FPM->add(createCFGSimplificationPass());
|
||||
FPM->add(createReassociatePass());
|
||||
FPM->add(createLoopRotatePass());
|
||||
FPM->add(createLICMPass());
|
||||
FPM->add(createLoopUnswitchPass(false));
|
||||
FPM->add(createInstructionCombiningPass());
|
||||
FPM->add(createIndVarSimplifyPass());
|
||||
FPM->add(createLoopDeletionPass());
|
||||
FPM->add(createLoopUnrollPass());
|
||||
FPM->add(createInstructionCombiningPass());
|
||||
FPM->add(createGVNPass());
|
||||
FPM->add(createSCCPPass());
|
||||
FPM->add(createInstructionCombiningPass());
|
||||
FPM->add(createJumpThreadingPass());
|
||||
FPM->add(createDeadStoreEliminationPass());
|
||||
FPM->add(createAggressiveDCEPass());
|
||||
FPM->add(createCFGSimplificationPass());
|
||||
|
||||
// Cache the LLVM type signature of an opcode function
|
||||
int_type = sizeof(size_t) == 4 ?
|
||||
IntegerType::getInt32Ty(Context) :
|
||||
IntegerType::getInt64Ty(Context);
|
||||
const Type *data_type =
|
||||
PointerType::getUnqual(IntegerType::getInt8Ty(Context));
|
||||
std::vector<const Type*> args;
|
||||
args.push_back(int_type);
|
||||
args.push_back(data_type);
|
||||
op_type =
|
||||
FunctionType::get(Type::getVoidTy(Context), args, false);
|
||||
|
||||
// Setup a global variable in the LLVM module to represent the bytecode
|
||||
// array. Bind it to the actual bytecode array at JIT time.
|
||||
const Type *bytecode_type = PointerType::getUnqual(op_type);
|
||||
bytecode_array = cast<GlobalValue>(module->
|
||||
getOrInsertGlobal("BytecodeArray", bytecode_type));
|
||||
EE->addGlobalMapping(bytecode_array, BytecodeArray);
|
||||
|
||||
// Setup a similar mapping for the global executed flag.
|
||||
const IntegerType *flag_type = IntegerType::get(Context, 8);
|
||||
executed_flag =
|
||||
cast<GlobalValue>(module->getOrInsertGlobal("executed", flag_type));
|
||||
EE->addGlobalMapping(executed_flag, &executed);
|
||||
|
||||
// Cache LLVM declarations for putchar() and getchar().
|
||||
const Type *int_type = sizeof(int) == 4 ? IntegerType::getInt32Ty(Context)
|
||||
: IntegerType::getInt64Ty(Context);
|
||||
putchar_func =
|
||||
module->getOrInsertFunction("putchar", int_type, int_type, NULL);
|
||||
getchar_func = module->getOrInsertFunction("getchar", int_type, NULL);
|
||||
}
|
||||
|
||||
void BrainFTraceRecorder::compile(BrainFTraceNode* trace) {
|
||||
LLVMContext &Context = module->getContext();
|
||||
|
||||
// Create a new function for the trace we're compiling.
|
||||
Function *curr_func = cast<Function>(module->
|
||||
getOrInsertFunction("trace_"+utostr(trace->pc), op_type));
|
||||
|
||||
// Create an entry block, which branches directly to a header block.
|
||||
// This is necessary because the entry block cannot be the target of
|
||||
// a loop.
|
||||
BasicBlock *Entry = BasicBlock::Create(Context, "entry", curr_func);
|
||||
Header = BasicBlock::Create(Context, utostr(trace->pc), curr_func);
|
||||
|
||||
// Mark the array pointer as noalias, and setup compiler state.
|
||||
IRBuilder<> builder(Entry);
|
||||
Argument *Arg1 = ++curr_func->arg_begin();
|
||||
Arg1->addAttr(Attribute::NoAlias);
|
||||
DataPtr = Arg1;
|
||||
|
||||
// Emit code to set the executed flag. This signals to the recorder
|
||||
// that the preceding opcode was executed as a part of a compiled trace.
|
||||
const IntegerType *flag_type = IntegerType::get(Context, 8);
|
||||
ConstantInt *True = ConstantInt::get(flag_type, 1);
|
||||
builder.CreateStore(True, executed_flag);
|
||||
builder.CreateBr(Header);
|
||||
|
||||
// Header will be the root of our trace tree. As such, all loop back-edges
|
||||
// will be targetting it. Setup a PHI node to merge together incoming values
|
||||
// for the current array pointer as we loop.
|
||||
builder.SetInsertPoint(Header);
|
||||
HeaderPHI = builder.CreatePHI(DataPtr->getType());
|
||||
HeaderPHI->addIncoming(DataPtr, Entry);
|
||||
DataPtr = HeaderPHI;
|
||||
|
||||
// Recursively descend the trace tree, emitting code for the opcodes as we go.
|
||||
compile_opcode(trace, builder);
|
||||
|
||||
// Run out optimization suite on our newly generated trace.
|
||||
FPM->run(*curr_func);
|
||||
|
||||
// Compile our trace to machine code, and install function pointer to it
|
||||
// into the bytecode array so that it will be executed every time the
|
||||
// trace-head PC is reached.
|
||||
void *code = EE->getPointerToFunction(curr_func);
|
||||
BytecodeArray[trace->pc] =
|
||||
(opcode_func_t)(intptr_t)code;
|
||||
}
|
||||
|
||||
/// compile_plus - Emit code for '+'
|
||||
void BrainFTraceRecorder::compile_plus(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
Value *CellValue = builder.CreateLoad(DataPtr);
|
||||
Constant *One =
|
||||
ConstantInt::get(IntegerType::getInt8Ty(Header->getContext()), 1);
|
||||
Value *UpdatedValue = builder.CreateAdd(CellValue, One);
|
||||
builder.CreateStore(UpdatedValue, DataPtr);
|
||||
|
||||
if (node->left != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->left, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
}
|
||||
|
||||
/// compile_minus - Emit code for '-'
|
||||
void BrainFTraceRecorder::compile_minus(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
Value *CellValue = builder.CreateLoad(DataPtr);
|
||||
Constant *One =
|
||||
ConstantInt::get(IntegerType::getInt8Ty(Header->getContext()), 1);
|
||||
Value *UpdatedValue = builder.CreateSub(CellValue, One);
|
||||
builder.CreateStore(UpdatedValue, DataPtr);
|
||||
|
||||
if (node->left != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->left, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
}
|
||||
|
||||
/// compile_left - Emit code for '<'
|
||||
void BrainFTraceRecorder::compile_left(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
Value *OldPtr = DataPtr;
|
||||
DataPtr = builder.CreateConstInBoundsGEP1_32(DataPtr, -1);
|
||||
if (node->left != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->left, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
DataPtr = OldPtr;
|
||||
}
|
||||
|
||||
/// compile_right - Emit code for '>'
|
||||
void BrainFTraceRecorder::compile_right(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
Value *OldPtr = DataPtr;
|
||||
DataPtr = builder.CreateConstInBoundsGEP1_32(DataPtr, 1);
|
||||
if (node->left != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->left, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
DataPtr = OldPtr;
|
||||
}
|
||||
|
||||
|
||||
/// compile_put - Emit code for '.'
|
||||
void BrainFTraceRecorder::compile_put(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
Value *Loaded = builder.CreateLoad(DataPtr);
|
||||
Value *Print =
|
||||
builder.CreateSExt(Loaded, IntegerType::get(Loaded->getContext(), 32));
|
||||
builder.CreateCall(putchar_func, Print);
|
||||
if (node->left != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->left, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
}
|
||||
|
||||
/// compile_get - Emit code for ','
|
||||
void BrainFTraceRecorder::compile_get(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
Value *Ret = builder.CreateCall(getchar_func);
|
||||
Value *Trunc =
|
||||
builder.CreateTrunc(Ret, IntegerType::get(Ret->getContext(), 8));
|
||||
builder.CreateStore(Ret, Trunc);
|
||||
if (node->left != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->left, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
}
|
||||
|
||||
/// compile_if - Emit code for '['
|
||||
void BrainFTraceRecorder::compile_if(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
BasicBlock *ZeroChild = 0;
|
||||
BasicBlock *NonZeroChild = 0;
|
||||
|
||||
IRBuilder<> oldBuilder = builder;
|
||||
|
||||
LLVMContext &Context = Header->getContext();
|
||||
|
||||
// If both directions of the branch go back to the trace-head, just
|
||||
// jump there directly.
|
||||
if (node->left == (BrainFTraceNode*)~0ULL &&
|
||||
node->right == (BrainFTraceNode*)~0ULL) {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, there are two cases to handle for each direction:
|
||||
// ~0ULL - A branch back to the trace head
|
||||
// 0 - A branch out of the trace
|
||||
// * - A branch to a node we haven't compiled yet.
|
||||
// Go ahead and generate code for both targets.
|
||||
|
||||
if (node->left == (BrainFTraceNode*)~0ULL) {
|
||||
NonZeroChild = Header;
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
} else if (node->left == 0) {
|
||||
NonZeroChild = BasicBlock::Create(Context,
|
||||
"exit_left_"+utostr(node->pc),
|
||||
Header->getParent());
|
||||
builder.SetInsertPoint(NonZeroChild);
|
||||
ConstantInt *NewPc = ConstantInt::get(int_type, node->pc+1);
|
||||
Value *BytecodeIndex =
|
||||
builder.CreateConstInBoundsGEP1_32(bytecode_array, node->pc+1);
|
||||
Value *Target = builder.CreateLoad(BytecodeIndex);
|
||||
CallInst *Call =cast<CallInst>(builder.CreateCall2(Target, NewPc, DataPtr));
|
||||
Call->setTailCall();
|
||||
builder.CreateRetVoid();
|
||||
} else {
|
||||
NonZeroChild = BasicBlock::Create(Context,
|
||||
utostr(node->left->pc),
|
||||
Header->getParent());
|
||||
builder.SetInsertPoint(NonZeroChild);
|
||||
compile_opcode(node->left, builder);
|
||||
}
|
||||
|
||||
if (node->right == (BrainFTraceNode*)~0ULL) {
|
||||
ZeroChild = Header;
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
} else if (node->right == 0) {
|
||||
ZeroChild = BasicBlock::Create(Context,
|
||||
"exit_right_"+utostr(node->pc),
|
||||
Header->getParent());
|
||||
builder.SetInsertPoint(ZeroChild);
|
||||
ConstantInt *NewPc = ConstantInt::get(int_type, JumpMap[node->pc]+1);
|
||||
Value *BytecodeIndex =
|
||||
builder.CreateConstInBoundsGEP1_32(bytecode_array, JumpMap[node->pc]+1);
|
||||
Value *Target = builder.CreateLoad(BytecodeIndex);
|
||||
CallInst *Call =cast<CallInst>(builder.CreateCall2(Target, NewPc, DataPtr));
|
||||
Call->setTailCall();
|
||||
builder.CreateRetVoid();
|
||||
} else {
|
||||
ZeroChild = BasicBlock::Create(Context,
|
||||
utostr(node->right->pc),
|
||||
Header->getParent());
|
||||
builder.SetInsertPoint(ZeroChild);
|
||||
compile_opcode(node->right, builder);
|
||||
}
|
||||
|
||||
// Generate the test and branch to select between the targets.
|
||||
Value *Loaded = oldBuilder.CreateLoad(DataPtr);
|
||||
Value *Cmp = oldBuilder.CreateICmpEQ(Loaded,
|
||||
ConstantInt::get(Loaded->getType(), 0));
|
||||
oldBuilder.CreateCondBr(Cmp, ZeroChild, NonZeroChild);
|
||||
}
|
||||
|
||||
/// compile_back - Emit code for ']'
|
||||
void BrainFTraceRecorder::compile_back(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
if (node->right != (BrainFTraceNode*)~0ULL)
|
||||
compile_opcode(node->right, builder);
|
||||
else {
|
||||
HeaderPHI->addIncoming(DataPtr, builder.GetInsertBlock());
|
||||
builder.CreateBr(Header);
|
||||
}
|
||||
}
|
||||
|
||||
/// compile_opcode - Dispatch to a more specific compiler function based
|
||||
/// on the opcode of the current node.
|
||||
void BrainFTraceRecorder::compile_opcode(BrainFTraceNode *node,
|
||||
IRBuilder<>& builder) {
|
||||
switch (node->opcode) {
|
||||
case '+':
|
||||
compile_plus(node, builder);
|
||||
break;
|
||||
case '-':
|
||||
compile_minus(node, builder);
|
||||
break;
|
||||
case '<':
|
||||
compile_left(node, builder);
|
||||
break;
|
||||
case '>':
|
||||
compile_right(node, builder);
|
||||
break;
|
||||
case '.':
|
||||
compile_put(node, builder);
|
||||
break;
|
||||
case ',':
|
||||
compile_get(node, builder);
|
||||
break;
|
||||
case '[':
|
||||
compile_if(node, builder);
|
||||
break;
|
||||
case ']':
|
||||
compile_back(node, builder);
|
||||
break;
|
||||
}
|
||||
}
|
116
examples/TracingBrainF/BrainFInterpreter.cpp
Normal file
116
examples/TracingBrainF/BrainFInterpreter.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
//===-- BrainFInterpreter.cpp - BrainF trace compiler interpreter -------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
#include "BrainF.h"
|
||||
#include "BrainFVM.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <cstdio>
|
||||
using namespace llvm;
|
||||
|
||||
//Command line options
|
||||
|
||||
static cl::opt<std::string>
|
||||
InputFilename(cl::Positional, cl::desc("<input brainf>"));
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
cl::ParseCommandLineOptions(argc, argv, " BrainF compiler\n");
|
||||
|
||||
if (InputFilename == "") {
|
||||
errs() << "Error: You must specify the filename of the program to "
|
||||
"be compiled. Use --help to see the options.\n";
|
||||
abort();
|
||||
}
|
||||
|
||||
// Read the input file.
|
||||
MemoryBuffer *Code = MemoryBuffer::getFileOrSTDIN(InputFilename);
|
||||
const uint8_t *CodeBegin = (const uint8_t*)(Code->getBufferStart());
|
||||
|
||||
// Create a new buffer to hold the preprocessed code.
|
||||
MemoryBuffer *ParsedCode =
|
||||
MemoryBuffer::getNewMemBuffer(sizeof(opcode_func_t) *
|
||||
(Code->getBufferSize()+1));
|
||||
BytecodeArray = (opcode_func_t*)(ParsedCode->getBufferStart());
|
||||
size_t BytecodeOffset = 0;
|
||||
|
||||
// Create JumpMap, a special on-the-side data array used to implement
|
||||
// efficient jumps in the interpreter.
|
||||
JumpMap = new size_t[Code->getBufferSize()];
|
||||
memset(JumpMap, 0, sizeof(size_t) * Code->getBufferSize());
|
||||
std::vector<size_t> Stack;
|
||||
|
||||
// Preprocess the input source code, performing three tasks:
|
||||
// 1 - Remove non-instruction characters
|
||||
// 2 - Replace character literals with opcode function pointers
|
||||
// 3 - Precompute the jump targets for [ and ] instructions in JumpMap
|
||||
for (size_t i = 0; i < Code->getBufferSize(); ++i) {
|
||||
uint8_t opcode = CodeBegin[i];
|
||||
switch (opcode) {
|
||||
case '>':
|
||||
BytecodeArray[BytecodeOffset++] = &op_right;
|
||||
break;
|
||||
case '<':
|
||||
BytecodeArray[BytecodeOffset++] = &op_left;
|
||||
break;
|
||||
case '+':
|
||||
BytecodeArray[BytecodeOffset++] = &op_plus;
|
||||
break;
|
||||
case '-':
|
||||
BytecodeArray[BytecodeOffset++] = &op_minus;
|
||||
break;
|
||||
case '.':
|
||||
BytecodeArray[BytecodeOffset++] = &op_put;
|
||||
break;
|
||||
case ',':
|
||||
BytecodeArray[BytecodeOffset++] = &op_get;
|
||||
break;
|
||||
case '[':
|
||||
Stack.push_back(BytecodeOffset);
|
||||
BytecodeArray[BytecodeOffset++] = &op_if;
|
||||
break;
|
||||
case ']':
|
||||
JumpMap[Stack.back()] = BytecodeOffset;
|
||||
JumpMap[BytecodeOffset] = Stack.back();
|
||||
Stack.pop_back();
|
||||
BytecodeArray[BytecodeOffset++] = &op_back;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the suffix of the preprocessed source for op_exit.
|
||||
// Thus, if we reach the end of the source, the program will terminate.
|
||||
while (BytecodeOffset < Code->getBufferSize()+1) {
|
||||
BytecodeArray[BytecodeOffset++] = &op_end;
|
||||
}
|
||||
|
||||
// Setup the array.
|
||||
uint8_t *BrainFArray = new uint8_t[32768];
|
||||
memset(BrainFArray, 0, 32768);
|
||||
|
||||
// Setup the trace recorder.
|
||||
Recorder = new BrainFTraceRecorder();
|
||||
|
||||
// Main interpreter loop.
|
||||
// Note the lack of a explicit loop: every opcode is a tail-recursive
|
||||
// function that calls its own successor by indexing into BytecodeArray.
|
||||
uint8_t* data = BrainFArray;
|
||||
BytecodeArray[0](0, data);
|
||||
|
||||
//Clean up
|
||||
delete Recorder;
|
||||
delete Code;
|
||||
delete ParsedCode;
|
||||
delete[] BrainFArray;
|
||||
delete[] JumpMap;
|
||||
|
||||
return 0;
|
||||
}
|
68
examples/TracingBrainF/BrainFOpcodes.cpp
Normal file
68
examples/TracingBrainF/BrainFOpcodes.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
//===-- BrainFOpcodes.cpp - BrainF interpreter opcodes ------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
#include "BrainFVM.h"
|
||||
#include <cstdio>
|
||||
|
||||
opcode_func_t *BytecodeArray = 0;
|
||||
size_t *JumpMap = 0;
|
||||
uint8_t executed = 0;
|
||||
|
||||
BrainFTraceRecorder *Recorder = 0;
|
||||
|
||||
void op_plus(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, '+');
|
||||
*data += 1;
|
||||
BytecodeArray[pc+1](pc+1, data);
|
||||
}
|
||||
|
||||
void op_minus(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, '-');
|
||||
*data -= 1;
|
||||
BytecodeArray[pc+1](pc+1, data);
|
||||
}
|
||||
|
||||
void op_left(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, '<');
|
||||
BytecodeArray[pc+1](pc+1, data-1);
|
||||
}
|
||||
|
||||
void op_right(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, '>');
|
||||
BytecodeArray[pc+1](pc+1, data+1);
|
||||
}
|
||||
|
||||
void op_put(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, '.');
|
||||
putchar(*data);
|
||||
BytecodeArray[pc+1](pc+1, data);
|
||||
}
|
||||
|
||||
void op_get(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, ',');
|
||||
*data = getchar();
|
||||
BytecodeArray[pc+1](pc+1, data);
|
||||
}
|
||||
|
||||
void op_if(size_t pc, uint8_t *data) {
|
||||
Recorder->record(pc, '[');
|
||||
size_t new_pc = pc+1;
|
||||
if (!*data) new_pc = JumpMap[pc]+1;
|
||||
BytecodeArray[new_pc](new_pc, data);
|
||||
}
|
||||
|
||||
void op_back(size_t pc, uint8_t *data) {
|
||||
Recorder->record_simple(pc, ']');
|
||||
size_t new_pc = JumpMap[pc];
|
||||
BytecodeArray[new_pc](new_pc, data);
|
||||
}
|
||||
|
||||
void op_end(size_t, uint8_t *) {
|
||||
return;
|
||||
}
|
155
examples/TracingBrainF/BrainFTraceRecorder.cpp
Normal file
155
examples/TracingBrainF/BrainFTraceRecorder.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
//===-- BrainFTraceRecorder.cpp - BrainF trace recorder ------------------==//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===--------------------------------------------------------------------===//
|
||||
//
|
||||
// This class observes the execution trace of the interpreter, identifying
|
||||
// hot traces and eventually compiling them to native code.
|
||||
//
|
||||
// The operation of the recorder can be divided into four parts:
|
||||
// 1) Interation Counting - To identify hot traces, we track the execution
|
||||
// counts of all loop headers ('[' instructions). We use a fixed-size
|
||||
// array of counters for this, since lack of precision does not affect
|
||||
// correctness.
|
||||
//
|
||||
// 2) Trace Buffering - Once a header has passed a hotness threshold, we
|
||||
// begin buffering the execution trace beginning from that header the
|
||||
// next time it is executed. This buffer is of a fixed length, though
|
||||
// that choice can be tuned for performance. If the end of the buffer
|
||||
// is reached without execution returning to the header, we throw out
|
||||
// the trace.
|
||||
//
|
||||
// 3) Trace Commit - If the buffered trace returns to the header before
|
||||
// the buffer limit is reached, that trace is commited to form a trace
|
||||
// tree. This tree aggregates all execution traces that have been
|
||||
// observed originating from the header since it passed the hotness
|
||||
// threshold. The buffer is then cleared to allow a new trace to be
|
||||
// recorded.
|
||||
//
|
||||
// 4) Trace Compilation - Once a secondary hotness threshold is reached,
|
||||
// trace recording is terminated and the set of observed traces encoded
|
||||
// in the trace tree are compiled to native code, and a function pointer
|
||||
// to that trace is installed into the bytecode array in place of one of
|
||||
// the normal opcode functions. Details of this compilation are in
|
||||
// BrainFCodeGen.cpp
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
#include "BrainF.h"
|
||||
#include "BrainFVM.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
#define ITERATION_BUF_SIZE 1024
|
||||
#define TRACE_BUF_SIZE 256
|
||||
#define TRACE_THRESHOLD 50
|
||||
#define COMPILE_THRESHOLD 200
|
||||
|
||||
void BrainFTraceRecorder::BrainFTraceNode::dump(unsigned lvl) {
|
||||
for (unsigned i = 0; i < lvl; ++i)
|
||||
outs() << '.';
|
||||
outs() << opcode << " : " << pc << "\n";
|
||||
if (left && left != (BrainFTraceNode*)~0ULL) left->dump(lvl+1);
|
||||
if (right && right != (BrainFTraceNode*)~0ULL) right->dump(lvl+1);
|
||||
}
|
||||
|
||||
BrainFTraceRecorder::BrainFTraceRecorder()
|
||||
: prev_opcode('+'), iteration_count(new uint8_t[ITERATION_BUF_SIZE]),
|
||||
trace_begin(new std::pair<uint8_t, size_t>[TRACE_BUF_SIZE]),
|
||||
trace_end(trace_begin + TRACE_BUF_SIZE),
|
||||
trace_tail(trace_begin),
|
||||
module(new Module("BrainF", getGlobalContext())) {
|
||||
memset(iteration_count, 0, ITERATION_BUF_SIZE);
|
||||
memset(trace_begin, 0, sizeof(std::pair<uint8_t, size_t>) * TRACE_BUF_SIZE);
|
||||
|
||||
initialize_module();
|
||||
}
|
||||
|
||||
BrainFTraceRecorder::~BrainFTraceRecorder() {
|
||||
delete[] iteration_count;
|
||||
delete[] trace_begin;
|
||||
delete FPM;
|
||||
delete EE;
|
||||
}
|
||||
|
||||
void BrainFTraceRecorder::commit() {
|
||||
BrainFTraceNode *&Head = trace_map[trace_begin->second];
|
||||
if (!Head)
|
||||
Head = new BrainFTraceNode(trace_begin->first, trace_begin->second);
|
||||
|
||||
BrainFTraceNode *Parent = Head;
|
||||
std::pair<uint8_t, size_t> *trace_iter = trace_begin+1;
|
||||
while (trace_iter != trace_tail) {
|
||||
BrainFTraceNode *Child = 0;
|
||||
|
||||
if (trace_iter->second == Parent->pc+1) {
|
||||
if (Parent->left) Child = Parent->left;
|
||||
else Child = Parent->left =
|
||||
new BrainFTraceNode(trace_iter->first, trace_iter->second);
|
||||
} else {
|
||||
if (Parent->right) Child = Parent->right;
|
||||
else Child = Parent->right =
|
||||
new BrainFTraceNode(trace_iter->first, trace_iter->second);
|
||||
}
|
||||
|
||||
Parent = Child;
|
||||
++trace_iter;
|
||||
}
|
||||
|
||||
if (Parent->pc+1 == Head->pc)
|
||||
Parent->left = (BrainFTraceNode*)~0ULL;
|
||||
else
|
||||
Parent->right = (BrainFTraceNode*)~0ULL;
|
||||
}
|
||||
|
||||
void BrainFTraceRecorder::record_simple(size_t pc, uint8_t opcode) {
|
||||
if (executed) {
|
||||
executed = false;
|
||||
trace_tail = trace_begin;
|
||||
}
|
||||
|
||||
if (trace_tail != trace_begin) {
|
||||
if (trace_tail == trace_end) {
|
||||
trace_tail = trace_begin;
|
||||
} else {
|
||||
trace_tail->first = opcode;
|
||||
trace_tail->second = pc;
|
||||
++trace_tail;
|
||||
}
|
||||
}
|
||||
prev_opcode = opcode;
|
||||
}
|
||||
|
||||
void BrainFTraceRecorder::record(size_t pc, uint8_t opcode) {
|
||||
if (executed) {
|
||||
executed = false;
|
||||
trace_tail = trace_begin;
|
||||
}
|
||||
|
||||
if (trace_tail != trace_begin) {
|
||||
if (pc == trace_begin->second) {
|
||||
commit();
|
||||
trace_tail = trace_begin;
|
||||
} else if (trace_tail == trace_end) {
|
||||
trace_tail = trace_begin;
|
||||
} else {
|
||||
trace_tail->first = opcode;
|
||||
trace_tail->second = pc;
|
||||
++trace_tail;
|
||||
}
|
||||
} else if (opcode == '[' && prev_opcode == ']'){
|
||||
size_t hash = pc % ITERATION_BUF_SIZE;
|
||||
if (iteration_count[hash] == 255) iteration_count[hash] = 254;
|
||||
if (++iteration_count[hash] > COMPILE_THRESHOLD && trace_map.count(pc)) {
|
||||
compile(trace_map[pc]);
|
||||
} else if (++iteration_count[hash] > TRACE_THRESHOLD) {
|
||||
trace_tail->first = opcode;
|
||||
trace_tail->second = pc;
|
||||
++trace_tail;
|
||||
}
|
||||
}
|
||||
|
||||
prev_opcode = opcode;
|
||||
}
|
63
examples/TracingBrainF/BrainFVM.h
Normal file
63
examples/TracingBrainF/BrainFVM.h
Normal file
@ -0,0 +1,63 @@
|
||||
//===-- BrainFVM.h - BrainF interpreter header ----------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
#ifndef BRAINF_VM_H
|
||||
#define BRAINF_VM_H
|
||||
|
||||
#include "BrainF.h"
|
||||
#include "stdint.h"
|
||||
#include <cstring>
|
||||
|
||||
/// opcode_func_t - A function pointer signature for all opcode functions.
|
||||
typedef void(*opcode_func_t)(size_t pc, uint8_t* data);
|
||||
|
||||
/// BytecodeArray - An array of function pointers representing the
|
||||
/// source program. Indexed by PC address.
|
||||
extern opcode_func_t *BytecodeArray;
|
||||
|
||||
/// JumpMap - An array of on-the-side data used by the interpreter.
|
||||
/// Indexed by PC address.
|
||||
extern size_t *JumpMap;
|
||||
|
||||
/// executed - A flag indicating whether the preceding opcode was evaluated
|
||||
/// within a compiled trace execution. Used by the trace recorder.
|
||||
extern uint8_t executed;
|
||||
|
||||
/// Recorder - The trace recording engine.
|
||||
extern BrainFTraceRecorder *Recorder;
|
||||
|
||||
/// op_plus - Implements the '+' instruction.
|
||||
void op_plus(size_t, uint8_t*);
|
||||
|
||||
/// op_minus - Implements the '-' instruction.
|
||||
void op_minus(size_t, uint8_t*);
|
||||
|
||||
// op_left - Implements the '<' instruction.
|
||||
void op_left(size_t, uint8_t*);
|
||||
|
||||
// op_right - Implements the '>' instruction.
|
||||
void op_right(size_t, uint8_t*);
|
||||
|
||||
// op_put - Implements the '.' instruction.
|
||||
void op_put(size_t, uint8_t*);
|
||||
|
||||
// op_get - Implements the ',' instruction.
|
||||
void op_get(size_t, uint8_t*);
|
||||
|
||||
// op_if - Implements the '[' instruction.
|
||||
void op_if(size_t, uint8_t*);
|
||||
|
||||
// op_back - Implements the ']' instruction.
|
||||
void op_back(size_t, uint8_t*);
|
||||
|
||||
// op_end - Terminates an execution.
|
||||
void op_end(size_t, uint8_t*);
|
||||
|
||||
|
||||
#endif
|
17
examples/TracingBrainF/Makefile
Normal file
17
examples/TracingBrainF/Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
##===- examples/TracingBrainF/Makefile ------------------- -*- Makefile -*-===##
|
||||
#
|
||||
# The LLVM Compiler Infrastructure
|
||||
#
|
||||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
#
|
||||
##===----------------------------------------------------------------------===##
|
||||
LEVEL = ../..
|
||||
TOOLNAME = TracingBrainF
|
||||
EXAMPLE_TOOL = 1
|
||||
|
||||
CXXFLAGS += -foptimize-sibling-calls
|
||||
|
||||
LINK_COMPONENTS := scalaropts jit nativecodegen
|
||||
|
||||
include $(LEVEL)/Makefile.common
|
1
examples/TracingBrainF/README
Normal file
1
examples/TracingBrainF/README
Normal file
@ -0,0 +1 @@
|
||||
This is an example trace-based JIT for Brainfuck, using LLVM as its code generation engine. To compile it, simply drop this directory within llvm/examples in an LLVM source tree, and do `make` in that directory.
|
Loading…
Reference in New Issue
Block a user