Commit 50eb5bed authored by Chris Ramstad's avatar Chris Ramstad

Merge remote-tracking branch 'upstream/master'

parents d0863da3 e185374c
......@@ -477,7 +477,6 @@ void Assembler::cmp(Register reg1, Register reg2) {
reg1_idx -= 8;
}
if (reg2_idx >= 8) {
trap();
rex |= REX_B;
reg2_idx -= 8;
}
......
......@@ -14,425 +14,680 @@
#include "asm_writing/rewriter.h"
#include "asm_writing/assembler.h"
#include <vector>
#include "asm_writing/icinfo.h"
#include "core/common.h"
#include "core/stats.h"
namespace pyston {
using namespace pyston::assembler;
#define MAX_ARGS 16
static const assembler::Register allocatable_regs[] = {
assembler::RAX, assembler::RCX, assembler::RBX, assembler::RDX,
// no RSP
// no RBP
assembler::RDI, assembler::RSI, assembler::R8, assembler::R9, assembler::R10,
assembler::R11, assembler::R12, assembler::R13, assembler::R14, assembler::R15,
};
Register fromArgnum(int argnum) {
Location Location::forArg(int argnum) {
assert(argnum >= 0);
switch (argnum) {
case -5:
return RBP;
case -4:
return RSP;
case -3:
return R11;
case -2:
return R10;
case -1:
return RAX;
case 0:
return RDI;
return assembler::RDI;
case 1:
return RSI;
return assembler::RSI;
case 2:
return RDX;
return assembler::RDX;
case 3:
return RCX;
return assembler::RCX;
case 4:
return R8;
return assembler::R8;
case 5:
return R9;
return assembler::R9;
default:
break;
}
RELEASE_ASSERT(0, "%d", argnum);
int offset = (argnum - 6) * 8;
return Location(Stack, offset);
}
RewriterVar::RewriterVar(Rewriter* rewriter, int argnum, int version)
: rewriter(rewriter), argnum(argnum), version(version) {
// assert(rewriter.icentry.get());
assembler::Register Location::asRegister() const {
assert(type == Register);
return assembler::Register(regnum);
}
RewriterVar& RewriterVar::operator=(const RewriterVar& rhs) {
assert(rewriter == NULL || rewriter == rhs.rewriter);
rhs.assertValid();
rewriter = rhs.rewriter;
argnum = rhs.argnum;
version = rhs.version;
return *this;
assembler::XMMRegister Location::asXMMRegister() const {
assert(type == XMMRegister);
return assembler::XMMRegister(regnum);
}
#ifndef NDEBUG
void RewriterVar::assertValid() const {
assert(rewriter);
rewriter->checkVersion(argnum, version);
}
bool Location::isClobberedByCall() const {
if (type == Register) {
return !asRegister().isCalleeSave();
}
void RewriterVar::lock() {
assertValid();
rewriter->lock(argnum);
}
if (type == XMMRegister)
return true;
void RewriterVar::unlock() {
assertValid();
rewriter->unlock(argnum);
}
#endif
if (type == Scratch)
return false;
int RewriterVar::getArgnum() {
// assert(rewriter);
return argnum;
}
if (type == Constant)
return false;
RewriterVar RewriterVar::getAttr(int offset, int dest) {
assertValid();
if (type == Stack)
return false;
rewriter->assembler->mov(Indirect(fromArgnum(this->argnum), offset), fromArgnum(dest));
int version = rewriter->mutate(dest);
return RewriterVar(rewriter, dest, version);
RELEASE_ASSERT(0, "%d", type);
}
void RewriterVar::incAttr(int offset) {
assertValid();
void Location::dump() const {
if (type == Register) {
asRegister().dump();
return;
}
rewriter->assembler->inc(Indirect(fromArgnum(this->argnum), offset));
if (type == XMMRegister) {
printf("%%xmm%d\n", regnum);
return;
}
#ifndef NDEBUG
rewriter->changed_something = true;
#endif
}
if (type == Scratch) {
printf("scratch(%d)\n", scratch_offset);
return;
}
void RewriterVar::setAttr(int offset, const RewriterVar& val, bool user_visible) {
assertValid();
val.assertValid();
if (type == Constant) {
printf("imm(%d)\n", constant_val);
return;
}
rewriter->assembler->mov(fromArgnum(val.argnum), Indirect(fromArgnum(this->argnum), offset));
if (type == Stack) {
printf("stack(%d)\n", stack_offset);
return;
}
#ifndef NDEBUG
if (user_visible)
rewriter->changed_something = true;
#endif
RELEASE_ASSERT(0, "%d", type);
}
RewriterVar RewriterVar::move(int dest_argnum) {
RewriterVarUsage::RewriterVarUsage(RewriterVar* var) : var(var), done_using(false) {
var->incUse();
assert(var->rewriter);
}
void RewriterVarUsage::addGuard(uint64_t val) {
assertValid();
int version;
if (dest_argnum != this->argnum) {
assert(dest_argnum < 6);
Rewriter* rewriter = var->rewriter;
assembler::Assembler* assembler = rewriter->assembler;
if (this->argnum >= 6) {
if (1) {
int offset = (this->argnum - 6) * 8 + rewriter->pushes.size() * 8 + rewriter->alloca_bytes;
rewriter->assembler->mov(Indirect(RSP, offset), fromArgnum(dest_argnum));
} else {
int stack_size = rewriter->rewrite->getFuncStackSize();
ASSERT(stack_size > 0 && stack_size < (1 << 30), "%d", stack_size);
int offset = (this->argnum - 6) * 8 - (stack_size - 8);
rewriter->assembler->mov(Indirect(RBP, offset), fromArgnum(dest_argnum));
}
} else {
rewriter->assembler->mov(fromArgnum(this->argnum), fromArgnum(dest_argnum));
}
assert(!rewriter->done_guarding && "too late to add a guard!");
version = rewriter->mutate(dest_argnum);
assembler::Register this_reg = var->getInReg();
if (val < (-1L << 31) || val >= (1L << 31) - 1) {
assembler::Register reg = rewriter->allocReg(Location::any());
assembler->mov(assembler::Immediate(val), reg);
assembler->cmp(this_reg, reg);
} else {
version = this->version;
assembler->cmp(this_reg, assembler::Immediate(val));
}
return RewriterVar(rewriter, dest_argnum, version);
assembler->jne(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
}
void RewriterVar::addGuard(intptr_t val) {
assert(!rewriter->changed_something && "too late to add a guard!");
void RewriterVarUsage::addGuardNotEq(uint64_t val) {
assertValid();
rewriter->checkArgsValid();
Rewriter* rewriter = var->rewriter;
assembler::Assembler* assembler = rewriter->assembler;
int bytes = 8 * rewriter->pushes.size() + rewriter->alloca_bytes;
assert(!rewriter->done_guarding && "too late to add a guard!");
assembler::Register this_reg = var->getInReg();
if (val < (-1L << 31) || val >= (1L << 31) - 1) {
rewriter->assembler->push(RBP);
rewriter->assembler->mov(Immediate(val), RBP);
rewriter->assembler->cmp(fromArgnum(this->argnum), RBP);
rewriter->assembler->pop(RBP);
assembler::Register reg = rewriter->allocReg(Location::any());
assembler->mov(assembler::Immediate(val), reg);
assembler->cmp(this_reg, reg);
} else {
rewriter->assembler->cmp(fromArgnum(this->argnum), Immediate(val));
assembler->cmp(this_reg, assembler::Immediate(val));
}
rewriter->assembler->jne(JumpDestination::fromStart(rewriter->rewrite->getSlotSize() - bytes / 8));
assembler->je(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
}
void RewriterVar::addAttrGuard(int offset, intptr_t val) {
assert(!rewriter->changed_something && "too late to add a guard!");
void RewriterVarUsage::addAttrGuard(int offset, uint64_t val) {
assertValid();
rewriter->checkArgsValid();
Rewriter* rewriter = var->rewriter;
assembler::Assembler* assembler = rewriter->assembler;
int bytes = 8 * rewriter->pushes.size() + rewriter->alloca_bytes;
assert(!rewriter->done_guarding && "too late to add a guard!");
assembler::Register this_reg = var->getInReg();
if (val < (-1L << 31) || val >= (1L << 31) - 1) {
rewriter->assembler->push(RBP);
rewriter->assembler->mov(Immediate(val), RBP);
rewriter->assembler->cmp(Indirect(fromArgnum(this->argnum), offset), RBP);
rewriter->assembler->pop(RBP);
assembler::Register reg = rewriter->allocReg(Location::any());
assert(reg != this_reg);
assembler->mov(assembler::Immediate(val), reg);
assembler->cmp(assembler::Indirect(this_reg, offset), reg);
} else {
rewriter->assembler->cmp(Indirect(fromArgnum(this->argnum), offset), Immediate(val));
assembler->cmp(assembler::Indirect(this_reg, offset), assembler::Immediate(val));
}
rewriter->assembler->jne(JumpDestination::fromStart(rewriter->rewrite->getSlotSize() - bytes / 8));
assembler->jne(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
}
void RewriterVar::addGuardNotEq(intptr_t val) {
assert(!rewriter->changed_something && "too late to add a guard!");
RewriterVarUsage RewriterVarUsage::getAttr(int offset, KillFlag kill, Location dest) {
assertValid();
rewriter->checkArgsValid();
// Save these, since if we kill this register the var might disappear:
assembler::Register this_reg = var->getInReg();
Rewriter* rewriter = var->rewriter;
int bytes = 8 * rewriter->pushes.size() + rewriter->alloca_bytes;
rewriter->assembler->cmp(fromArgnum(this->argnum), Immediate(val));
rewriter->assembler->je(JumpDestination::fromStart(rewriter->rewrite->getSlotSize() - bytes / 8));
}
if (kill == Kill) {
setDoneUsing();
}
bool RewriterVar::isInReg() {
int num_arg_regs = 6;
return argnum < num_arg_regs;
assembler::Register newvar_reg = rewriter->allocReg(dest);
RewriterVarUsage newvar = rewriter->createNewVar(newvar_reg);
rewriter->assembler->mov(assembler::Indirect(this_reg, offset), newvar_reg);
return std::move(newvar);
}
void RewriterVar::push() {
RewriterVarUsage RewriterVarUsage::cmp(AST_TYPE::AST_TYPE cmp_type, RewriterVarUsage other, Location dest) {
assertValid();
assert(isInReg());
rewriter->assembler->push(fromArgnum(this->argnum));
rewriter->addPush(this->version);
}
RewriterVar RewriterVar::cmp(AST_TYPE::AST_TYPE cmp_type, const RewriterVar& val, int dest) {
assertValid();
assembler::Register this_reg = var->getInReg();
assembler::Register other_reg = other.var->getInReg();
assert(this_reg != other_reg); // TODO how do we ensure this?
Rewriter* rewriter = var->rewriter;
rewriter->assembler->cmp(fromArgnum(this->argnum), fromArgnum(val.argnum));
assembler::Register newvar_reg = rewriter->allocReg(dest);
rewriter->assembler->cmp(this_reg, other_reg);
RewriterVarUsage newvar = rewriter->createNewVar(newvar_reg);
switch (cmp_type) {
case AST_TYPE::Eq:
rewriter->assembler->sete(fromArgnum(dest));
rewriter->assembler->sete(newvar_reg);
break;
case AST_TYPE::NotEq:
rewriter->assembler->setne(fromArgnum(dest));
rewriter->assembler->setne(newvar_reg);
break;
default:
RELEASE_ASSERT(0, "%d", cmp_type);
}
int version = rewriter->mutate(dest);
return RewriterVar(rewriter, dest, version);
other.setDoneUsing();
return std::move(newvar);
}
RewriterVar RewriterVar::toBool(int dest) {
RewriterVarUsage RewriterVarUsage::toBool(KillFlag kill, Location dest) {
assertValid();
rewriter->assembler->test(fromArgnum(this->argnum), fromArgnum(this->argnum));
rewriter->assembler->setnz(fromArgnum(dest));
assembler::Register this_reg = var->getInReg();
Rewriter* rewriter = var->rewriter;
if (kill == Kill) {
setDoneUsing();
}
rewriter->assembler->test(this_reg, this_reg);
assembler::Register result_reg = rewriter->allocReg(dest);
rewriter->assembler->setnz(result_reg);
int version = rewriter->mutate(dest);
return RewriterVar(rewriter, dest, version);
RewriterVarUsage result = rewriter->createNewVar(result_reg);
return result;
}
RewriterVar RewriterVar::add(int64_t amount) {
void RewriterVarUsage::setAttr(int offset, RewriterVarUsage val) {
assertValid();
var->rewriter->assertChangesOk();
if (amount > 0)
rewriter->assembler->add(assembler::Immediate(amount), fromArgnum(this->argnum));
else
rewriter->assembler->sub(assembler::Immediate(-amount), fromArgnum(this->argnum));
int new_version = rewriter->mutate(this->argnum);
return RewriterVar(rewriter, this->argnum, new_version);
}
assembler::Register this_reg = var->getInReg();
Rewriter* Rewriter::createRewriter(void* ic_rtn_addr, int num_orig_args, int num_temp_regs, const char* debug_name) {
assert(num_temp_regs <= 2 && "unsupported");
bool is_immediate;
assembler::Immediate imm = val.var->tryGetAsImmediate(&is_immediate);
static StatCounter rewriter_nopatch("rewriter_nopatch");
if (is_immediate) {
var->rewriter->assembler->movq(imm, assembler::Indirect(this_reg, offset));
} else {
assembler::Register other_reg = val.var->getInReg();
ICInfo* ic = getICInfo(ic_rtn_addr);
if (ic == NULL) {
rewriter_nopatch.log();
return NULL;
// TODO the allocator could choose to spill this_reg in order to load other_reg...
// Hopefuly it won't make that decision, so we should just be able to guard on it for now:
assert(this_reg != other_reg);
var->rewriter->assembler->mov(other_reg, assembler::Indirect(this_reg, offset));
}
assert(ic->getCallingConvention() == llvm::CallingConv::C && "Rewriter[1] only supports the C calling convention!");
return new Rewriter(ic->startRewrite(debug_name), num_orig_args, num_temp_regs);
val.setDoneUsing();
}
Rewriter::Rewriter(ICSlotRewrite* rewrite, int num_orig_args, int num_temp_regs)
: rewrite(rewrite), assembler(rewrite->getAssembler()), num_orig_args(num_orig_args), num_temp_regs(num_temp_regs),
alloca_bytes(0), max_pushes(0)
#ifndef NDEBUG
,
next_version(2), changed_something(false)
#endif
,
ndecisions(0), decision_path(1) {
// printf("trapping here\n");
// assembler->trap();
void RewriterVarUsage::setDoneUsing() {
assertValid();
done_using = true;
var->decUse();
var = NULL;
}
// for (int i = 0; i < num_temp_regs; i++) {
// icentry->push(-2 - i);
//}
bool RewriterVarUsage::isDoneUsing() {
return done_using;
}
#ifndef NDEBUG
for (int i = -5; i < MAX_ARGS; i++) {
versions[i] = next_version++;
}
#endif
void RewriterVarUsage::ensureDoneUsing() {
if (!done_using)
setDoneUsing();
}
void Rewriter::addPush(int version) {
pushes.push_back(version);
max_pushes = std::max(max_pushes, (int)pushes.size());
RewriterVarUsage::RewriterVarUsage(RewriterVarUsage&& usage) {
assert(!usage.done_using);
assert(usage.var != NULL);
var = usage.var;
done_using = usage.done_using;
usage.var = NULL;
usage.done_using = true;
}
RewriterVar Rewriter::alloca_(int bytes, int dest_argnum) {
// TODO should check to make sure we aren't crossing push+pops and allocas
// printf("alloca()ing %d bytes\n", bytes);
assert(bytes % sizeof(void*) == 0);
alloca_bytes += bytes;
RewriterVarUsage& RewriterVarUsage::operator=(RewriterVarUsage&& usage) {
assert(done_using);
assert(var == NULL);
assert(!usage.done_using);
assert(usage.var != NULL);
var = usage.var;
done_using = usage.done_using;
assembler->sub(Immediate(bytes), RSP);
assembler->mov(RSP, fromArgnum(dest_argnum));
usage.var = NULL;
usage.done_using = true;
int version = mutate(dest_argnum);
return RewriterVar(this, dest_argnum, version);
return *this;
}
RewriterVar Rewriter::getArg(int argnum) {
assert(argnum >= -1);
assert(argnum < MAX_ARGS);
#ifndef NDEBUG
int version = versions[argnum];
assert(version);
assert(version == argnum + 7);
#else
int version = 0;
#endif
return RewriterVar(this, argnum, version);
void RewriterVar::dump() {
printf("RewriterVar at %p: %d uses. %ld locations:\n", this, num_uses, locations.size());
for (Location l : locations)
l.dump();
}
assembler::Immediate RewriterVar::tryGetAsImmediate(bool* is_immediate) {
for (Location l : locations) {
if (l.type == Location::Constant) {
*is_immediate = true;
return assembler::Immediate(l.constant_val);
}
}
*is_immediate = false;
return assembler::Immediate((uint64_t)0);
}
RewriterVar Rewriter::getRsp() {
int argnum = -4;
assembler::Register RewriterVar::getInReg(Location dest) {
assert(dest.type == Location::Register || dest.type == Location::AnyReg);
// assembler::Register reg = var->rewriter->allocReg(l);
// var->rewriter->addLocationToVar(var, reg);
// return reg;
assert(locations.size());
#ifndef NDEBUG
int version = versions[argnum];
#else
int version = 0;
for (Location l : locations) {
ASSERT(l.type != Location::Constant, "why do you want this in a register?");
}
#endif
assert(version);
return RewriterVar(this, argnum, version);
// Not sure if this is worth it,
// but first try to see if we're already in this specific register
for (Location l : locations) {
if (l == dest)
return l.asRegister();
}
// Then, see if we're in another register
for (Location l : locations) {
if (l.type == Location::Register) {
assembler::Register reg = l.asRegister();
if (dest.type != Location::AnyReg) {
assembler::Register dest_reg = dest.asRegister();
assert(dest_reg != reg); // should have been caught by the previous case
rewriter->assembler->mov(reg, dest_reg);
rewriter->addLocationToVar(this, dest_reg);
return dest_reg;
}
return reg;
}
}
assert(locations.size() == 1);
Location l(*locations.begin());
assert(l.type == Location::Scratch || l.type == Location::Stack);
assembler::Register reg = rewriter->allocReg(dest);
assert(rewriter->vars_by_location.count(reg) == 0);
assembler::Indirect mem = rewriter->indirectFor(l);
rewriter->assembler->mov(mem, reg);
rewriter->addLocationToVar(this, reg);
return reg;
}
RewriterVar Rewriter::getRbp() {
int argnum = -5;
assembler::XMMRegister RewriterVar::getInXMMReg(Location dest) {
assert(dest.type == Location::XMMRegister || dest.type == Location::AnyReg);
assert(locations.size());
#ifndef NDEBUG
int version = versions[argnum];
#else
int version = 0;
for (Location l : locations) {
ASSERT(l.type != Location::Constant, "why do you want this in a register?");
}
#endif
assert(version);
return RewriterVar(this, argnum, version);
// Not sure if this is worth it,
// but first try to see if we're already in this specific register
for (Location l : locations) {
if (l == dest)
return l.asXMMRegister();
}
// Then, see if we're in another register
for (Location l : locations) {
if (l.type == Location::XMMRegister) {
assembler::XMMRegister reg = l.asXMMRegister();
if (dest.type != Location::AnyReg) {
assembler::XMMRegister dest_reg = dest.asXMMRegister();
assert(dest_reg != reg); // should have been caught by the previous case
rewriter->assembler->movsd(reg, dest_reg);
rewriter->addLocationToVar(this, dest_reg);
return dest_reg;
}
return reg;
}
}
assert(locations.size() == 1);
Location l(*locations.begin());
assert(l.type == Location::Scratch);
assert(dest.type == Location::XMMRegister);
assembler::XMMRegister reg = dest.asXMMRegister();
assert(rewriter->vars_by_location.count(reg) == 0);
assembler::Indirect mem = rewriter->indirectFor(l);
rewriter->assembler->movsd(mem, reg);
rewriter->addLocationToVar(this, reg);
return reg;
}
#ifndef NDEBUG
void Rewriter::checkArgsValid() {
for (int i = 0; i < num_orig_args; i++)
checkVersion(i, i + 7);
RewriterVarUsage::RewriterVarUsage() : var(NULL), done_using(true) {
}
int Rewriter::mutate(int argnum) {
ASSERT(locked.count(argnum) == 0, "arg %d is locked!", argnum);
assert(versions.count(argnum));
RewriterVarUsage RewriterVarUsage::empty() {
return RewriterVarUsage();
}
void RewriterVar::decUse() {
num_uses--;
if (num_uses == 0) {
rewriter->kill(this);
delete this;
}
}
void RewriterVar::incUse() {
num_uses++;
}
int rtn_version = ++next_version;
// printf("mutating %d to %d\n", argnum, rtn_version);
versions[argnum] = rtn_version;
return rtn_version;
bool RewriterVar::isInLocation(Location l) {
return locations.count(l) != 0;
}
void Rewriter::lock(int argnum) {
assert(locked.count(argnum) == 0);
locked.insert(argnum);
void Rewriter::setDoneGuarding() {
assert(!done_guarding);
done_guarding = true;
for (RewriterVar* var : args) {
var->decUse();
}
args.clear();
}
void Rewriter::unlock(int argnum) {
assert(locked.count(argnum) == 1);
locked.erase(argnum);
RewriterVarUsage Rewriter::getArg(int argnum) {
assert(!done_guarding);
assert(argnum >= 0 && argnum < args.size());
RewriterVar* var = args[argnum];
return RewriterVarUsage(var);
}
void Rewriter::checkVersion(int argnum, int version) {
assert(version > 0);
ASSERT(version == versions[argnum], "arg %d got updated from %d to %d", argnum, version, versions[argnum]);
Location Rewriter::getReturnDestination() {
return return_location;
}
#endif
void Rewriter::trap() {
assembler->trap();
}
void Rewriter::nop() {
assembler->nop();
RewriterVarUsage Rewriter::loadConst(int64_t val, Location dest) {
if (val >= (-1L << 31) && val < (1L << 31) - 1) {
Location l(Location::Constant, val);
RewriterVar*& var = vars_by_location[l];
if (!var) {
var = new RewriterVar(this, l);
}
return RewriterVarUsage(var);
}
assembler::Register reg = allocReg(dest);
RewriterVarUsage var = createNewVar(reg);
assembler->mov(assembler::Immediate(val), reg);
// I guess you don't need std::move here:
return var;
}
void Rewriter::annotate(int num) {
assembler->emitAnnotation(num);
RewriterVarUsage Rewriter::call(bool can_call_into_python, void* func_addr, RewriterVarUsage arg0) {
std::vector<RewriterVarUsage> args;
args.push_back(std::move(arg0));
return call(can_call_into_python, func_addr, std::move(args));
}
RewriterVar Rewriter::loadConst(int argnum, intptr_t val) {
assembler->mov(Immediate(val), fromArgnum(argnum));
int version = mutate(argnum);
return RewriterVar(this, argnum, version);
RewriterVarUsage Rewriter::call(bool can_call_into_python, void* func_addr, RewriterVarUsage arg0,
RewriterVarUsage arg1) {
std::vector<RewriterVarUsage> args;
args.push_back(std::move(arg0));
args.push_back(std::move(arg1));
return call(can_call_into_python, func_addr, std::move(args));
}
RewriterVar Rewriter::call(void* func_addr) {
static const Location caller_save_registers[]{
assembler::RAX, assembler::RCX, assembler::RDX, assembler::RSI, assembler::RDI,
assembler::R8, assembler::R9, assembler::R10, assembler::R11, assembler::XMM0,
assembler::XMM1, assembler::XMM2, assembler::XMM3, assembler::XMM4, assembler::XMM5,
assembler::XMM6, assembler::XMM7, assembler::XMM8, assembler::XMM9, assembler::XMM10,
assembler::XMM11, assembler::XMM12, assembler::XMM13, assembler::XMM14, assembler::XMM15,
};
RewriterVarUsage Rewriter::call(bool can_call_into_python, void* func_addr, std::vector<RewriterVarUsage> args) {
// TODO figure out why this is here -- what needs to be done differently
// if can_call_into_python is true?
// assert(!can_call_into_python);
assertChangesOk();
// RewriterVarUsage scratch = createNewVar(Location::any());
assembler::Register r = allocReg(assembler::R11);
for (int i = 0; i < args.size(); i++) {
Location l(Location::forArg(i));
RewriterVar* var = args[i].var;
// printf("%d ", i);
// var->dump();
if (!var->isInLocation(l)) {
assembler::Register r = l.asRegister();
{
// this forces the register allocator to spill this register:
assembler::Register r2 = allocReg(l);
assert(r == r2);
assert(vars_by_location.count(l) == 0);
}
// FIXME: get rid of tryGetAsImmediate
// instead do that work here; ex this could be a stack location
bool is_immediate;
assembler::Immediate imm = var->tryGetAsImmediate(&is_immediate);
if (is_immediate) {
assembler->mov(imm, r);
addLocationToVar(var, l);
} else {
assembler::Register r2 = var->getInReg(l);
assert(var->locations.count(r2));
assert(r2 == r);
}
}
assert(var->isInLocation(Location::forArg(i)));
}
#ifndef NDEBUG
changed_something = true;
for (int i = 0; i < args.size(); i++) {
RewriterVar* var = args[i].var;
if (!var->isInLocation(Location::forArg(i))) {
var->dump();
}
assert(var->isInLocation(Location::forArg(i)));
}
#endif
// printf("%ld pushes, %d alloca bytes\n", pushes.size(), alloca_bytes);
int bytes = 8 * pushes.size() + alloca_bytes;
bool didpush;
if (bytes % 16 == 8) {
assembler->push(RDI);
didpush = true;
} else {
assert(bytes % 16 == 0);
didpush = false;
// Spill caller-saved registers:
for (auto check_reg : caller_save_registers) {
// check_reg.dump();
assert(check_reg.isClobberedByCall());
auto it = vars_by_location.find(check_reg);
if (it == vars_by_location.end())
continue;
RewriterVar* var = it->second;
bool need_to_spill = true;
for (Location l : var->locations) {
if (!l.isClobberedByCall()) {
need_to_spill = false;
break;
}
}
for (int i = 0; i < args.size(); i++) {
if (args[i].var == var) {
if (var->num_uses == 1) {
// If we hold the only usage of this arg var, we are
// going to kill all of its usages soon anyway,
// so we have no need to spill it.
need_to_spill = false;
}
break;
}
}
if (need_to_spill) {
if (check_reg.type == Location::Register) {
spillRegister(check_reg.asRegister());
} else {
assert(check_reg.type == Location::XMMRegister);
assert(var->locations.size() == 1);
spillRegister(check_reg.asXMMRegister());
}
} else {
removeLocationFromVar(var, check_reg);
}
}
assembler->emitCall(func_addr, R11);
// We call setDoneUsing after spilling because when we release these,
// we might release a pointer to an array in the scratch space allocated
// with _allocate. If we do that before spilling, we might spill into that
// scratch space.
for (int i = 0; i < args.size(); i++) {
args[i].setDoneUsing();
}
if (didpush)
assembler->pop(RDI);
#ifndef NDEBUG
int num_arg_regs = 6;
for (int i = -3; i < num_arg_regs; i++) {
mutate(i);
for (const auto& p : vars_by_location) {
Location l = p.first;
// l.dump();
if (l.isClobberedByCall()) {
p.second->dump();
}
assert(!l.isClobberedByCall());
}
#endif
return RewriterVar(this, -1, mutate(-1));
assembler->mov(assembler::Immediate(func_addr), r);
assembler->callq(r);
assert(vars_by_location.count(assembler::RAX) == 0);
RewriterVar* var = vars_by_location[assembler::RAX] = new RewriterVar(this, assembler::RAX);
return RewriterVarUsage(var);
}
RewriterVar Rewriter::pop(int argnum) {
assert(pushes.size() > 0);
void Rewriter::commit() {
static StatCounter rewriter2_commits("rewriter2_commits");
rewriter2_commits.log();
assert(done_guarding && "Could call setDoneGuarding for you, but probably best to do it yourself");
// if (!done_guarding)
// setDoneGuarding();
assert(live_out_regs.size() == live_outs.size());
for (int i = 0; i < live_outs.size(); i++) {
assembler::GenericRegister ru = assembler::GenericRegister::fromDwarf(live_out_regs[i]);
Location expected(ru);
RewriterVar* var = live_outs[i];
// for (Location l : var->locations) {
// printf("%d %d\n", l.type, l._data);
//}
if (!var->isInLocation(expected)) {
assert(vars_by_location.count(expected) == 0);
if (ru.type == assembler::GenericRegister::GP) {
assembler::Register reg = var->getInReg(ru.gp);
assert(reg == ru.gp);
} else if (ru.type == assembler::GenericRegister::XMM) {
assembler::XMMRegister reg = var->getInXMMReg(ru.xmm);
assert(reg == ru.xmm);
} else {
RELEASE_ASSERT(0, "%d", ru.type);
}
}
int version = pushes.back();
pushes.pop_back();
#ifndef NDEBUG
versions[argnum] = version;
#endif
// printf("popping %d to %d\n", version, argnum);
assert(var->isInLocation(ru));
var->decUse();
}
assembler->pop(fromArgnum(argnum));
return RewriterVar(this, argnum, version);
assert(vars_by_location.size() == 0);
rewrite->commit(decision_path, this);
}
void Rewriter::finishAssembly(int continue_offset) {
assembler->jmp(assembler::JumpDestination::fromStart(continue_offset));
assembler->fillWithNops();
}
void Rewriter::commitReturning(RewriterVarUsage usage) {
// assert(usage.var->isInLocation(getReturnDestination()));
usage.var->getInReg(getReturnDestination());
usage.setDoneUsing();
commit();
}
void Rewriter::addDecision(int way) {
......@@ -445,23 +700,294 @@ void Rewriter::addDependenceOn(ICInvalidator& invalidator) {
rewrite->addDependenceOn(invalidator);
}
void Rewriter::commit() {
static StatCounter rewriter_commits("rewriter_commits");
rewriter_commits.log();
void Rewriter::kill(RewriterVar* var) {
for (RewriterVarUsage& scratch_range_usage : var->scratch_range) {
// Should be the only usage for this particular var (we
// hold the only usage) so it should cause the array to
// be deallocated.
scratch_range_usage.setDoneUsing();
}
var->scratch_range.clear();
// make sure we left the stack the way we found it:
assert(pushes.size() == 0);
assert(alloca_bytes == 0);
for (Location l : var->locations) {
assert(vars_by_location[l] == var);
vars_by_location.erase(l);
}
}
rewrite->commit(decision_path, this);
Location Rewriter::allocScratch() {
int scratch_bytes = rewrite->getScratchBytes();
for (int i = 0; i < scratch_bytes; i += 8) {
Location l(Location::Scratch, i);
if (vars_by_location.count(l) == 0) {
return l;
}
}
RELEASE_ASSERT(0, "Using all %d bytes of scratch!", scratch_bytes);
}
std::pair<RewriterVarUsage, int> Rewriter::_allocate(int n) {
assert(n >= 1);
int scratch_bytes = rewrite->getScratchBytes();
int consec = 0;
for (int i = 0; i < scratch_bytes; i += 8) {
Location l(Location::Scratch, i);
if (vars_by_location.count(l) == 0) {
consec++;
if (consec == n) {
int a = i / 8 - n + 1;
int b = i / 8;
assembler::Register r = allocReg(Location::any());
// TODO should be a LEA instruction
// In fact, we could do something like we do for constants and only load
// this when necessary, so it won't spill. Is that worth?
assembler->mov(assembler::RBP, r);
assembler->add(assembler::Immediate(8 * a + rewrite->getScratchRbpOffset()), r);
RewriterVarUsage usage = createNewVar(r);
for (int j = a; j <= b; j++) {
Location m(Location::Scratch, j * 8);
RewriterVarUsage placeholder = createNewVar(m);
usage.var->scratch_range.push_back(std::move(placeholder));
}
return std::make_pair(std::move(usage), a);
}
} else {
consec = 0;
}
}
RELEASE_ASSERT(0, "Using all %d bytes of scratch!", scratch_bytes);
}
void Rewriter::finishAssembly(int continue_offset) {
assembler->jmp(JumpDestination::fromStart(continue_offset));
RewriterVarUsage Rewriter::allocate(int n) {
return _allocate(n).first;
}
RewriterVarUsage Rewriter::allocateAndCopy(RewriterVarUsage array_ptr, int n) {
// TODO smart register allocation
array_ptr.assertValid();
std::pair<RewriterVarUsage, int> allocation = _allocate(n);
int offset = allocation.second;
assembler::Register tmp = allocReg(Location::any());
assembler::Register src_ptr = array_ptr.var->getInReg();
assert(tmp != src_ptr); // TODO how to ensure this?
for (int i = 0; i < n; i++) {
assembler->mov(assembler::Indirect(src_ptr, 8 * i), tmp);
assembler->mov(tmp, assembler::Indirect(assembler::RBP, 8 * (offset + i) + rewrite->getScratchRbpOffset()));
}
array_ptr.setDoneUsing();
return std::move(allocation.first);
}
RewriterVarUsage Rewriter::allocateAndCopyPlus1(RewriterVarUsage first_elem, RewriterVarUsage rest_ptr, int n_rest) {
first_elem.assertValid();
if (n_rest > 0)
rest_ptr.assertValid();
else
assert(rest_ptr.isDoneUsing());
std::pair<RewriterVarUsage, int> allocation = _allocate(n_rest + 1);
int offset = allocation.second;
assembler::Register tmp = first_elem.var->getInReg();
assembler->mov(tmp, assembler::Indirect(assembler::RBP, 8 * offset + rewrite->getScratchRbpOffset()));
if (n_rest > 0) {
assembler::Register src_ptr = rest_ptr.var->getInReg();
// TODO if this triggers we'll need a way to allocate two distinct registers
assert(tmp != src_ptr);
for (int i = 0; i < n_rest; i++) {
assembler->mov(assembler::Indirect(src_ptr, 8 * i), tmp);
assembler->mov(tmp,
assembler::Indirect(assembler::RBP, 8 * (offset + i + 1) + rewrite->getScratchRbpOffset()));
}
rest_ptr.setDoneUsing();
}
first_elem.setDoneUsing();
assembler->fillWithNopsExcept(max_pushes);
for (int i = 0; i < max_pushes; i++) {
assembler->pop(RAX);
return std::move(allocation.first);
}
assembler::Indirect Rewriter::indirectFor(Location l) {
assert(l.type == Location::Scratch || l.type == Location::Stack);
if (l.type == Location::Scratch)
// TODO it can sometimes be more efficient to do RSP-relative addressing?
return assembler::Indirect(assembler::RBP, rewrite->getScratchRbpOffset() + l.scratch_offset);
else
return assembler::Indirect(assembler::RSP, l.stack_offset);
}
void Rewriter::spillRegister(assembler::Register reg) {
// Don't spill a register than an input argument is in, unless
// we are done guarding (in which case `args` will be empty)
for (int i = 0; i < args.size(); i++) {
assert(!args[i]->isInLocation(Location(reg)));
}
RewriterVar* var = vars_by_location[reg];
assert(var);
// First, try to spill into a callee-save register:
for (assembler::Register new_reg : allocatable_regs) {
if (!new_reg.isCalleeSave())
continue;
if (vars_by_location.count(new_reg))
continue;
assembler->mov(reg, new_reg);
addLocationToVar(var, new_reg);
removeLocationFromVar(var, reg);
return;
}
Location scratch = allocScratch();
assembler::Indirect mem = indirectFor(scratch);
assembler->mov(reg, mem);
addLocationToVar(var, scratch);
removeLocationFromVar(var, reg);
}
void Rewriter::spillRegister(assembler::XMMRegister reg) {
assert(done_guarding);
RewriterVar* var = vars_by_location[reg];
assert(var);
assert(var->locations.size() == 1);
Location scratch = allocScratch();
assembler::Indirect mem = indirectFor(scratch);
assembler->movsd(reg, mem);
addLocationToVar(var, scratch);
removeLocationFromVar(var, reg);
}
assembler::Register Rewriter::allocReg(Location dest) {
if (dest.type == Location::AnyReg) {
for (assembler::Register reg : allocatable_regs) {
if (vars_by_location.count(reg) == 0)
return reg;
}
return allocReg(assembler::R15); // seem as fine as any
} else if (dest.type == Location::Register) {
assembler::Register reg(dest.regnum);
if (vars_by_location.count(reg)) {
spillRegister(reg);
}
assert(vars_by_location.count(reg) == 0);
return reg;
} else {
RELEASE_ASSERT(0, "%d", dest.type);
}
}
void Rewriter::addLocationToVar(RewriterVar* var, Location l) {
assert(!var->isInLocation(l));
assert(vars_by_location.count(l) == 0);
ASSERT(l.type == Location::Register || l.type == Location::XMMRegister || l.type == Location::Scratch, "%d",
l.type);
var->locations.insert(l);
vars_by_location[l] = var;
}
void Rewriter::removeLocationFromVar(RewriterVar* var, Location l) {
assert(var->isInLocation(l));
assert(vars_by_location[l] == var);
vars_by_location.erase(l);
var->locations.erase(l);
}
RewriterVarUsage Rewriter::createNewVar(Location dest) {
RewriterVar*& var = vars_by_location[dest];
assert(!var);
var = new RewriterVar(this, dest);
return var;
}
TypeRecorder* Rewriter::getTypeRecorder() {
return rewrite->getTypeRecorder();
}
Rewriter::Rewriter(ICSlotRewrite* rewrite, int num_args, const std::vector<int>& live_outs)
: rewrite(rewrite), assembler(rewrite->getAssembler()), return_location(rewrite->returnRegister()),
done_guarding(false), ndecisions(0), decision_path(1) {
// assembler->trap();
for (int i = 0; i < num_args; i++) {
Location l = Location::forArg(i);
RewriterVar* var = new RewriterVar(this, l);
vars_by_location[l] = var;
var->incUse();
args.push_back(var);
}
static StatCounter rewriter_starts("rewriter2_starts");
rewriter_starts.log();
static StatCounter rewriter_spillsavoided("rewriter2_spillsavoided");
// Calculate the list of live-ins based off the live-outs list,
// and create a Use of them so that they get preserved
for (int dwarf_regnum : live_outs) {
assembler::GenericRegister ru = assembler::GenericRegister::fromDwarf(dwarf_regnum);
Location l(ru);
// We could handle this here, but for now we're assuming that the return destination
// will get removed from this list before it gets handed to us.
assert(l != getReturnDestination());
//// The return register is the only live-out that is not also a live-in.
// if (l == getReturnDestination()) {
// l.dump();
// continue;
//}
if (l.isClobberedByCall()) {
rewriter_spillsavoided.log();
}
RewriterVar*& var = vars_by_location[l];
if (!var) {
var = new RewriterVar(this, l);
}
var->incUse();
this->live_outs.push_back(var);
this->live_out_regs.push_back(dwarf_regnum);
}
}
Rewriter* Rewriter::createRewriter(void* rtn_addr, int num_args, const char* debug_name) {
ICInfo* ic = getICInfo(rtn_addr);
static StatCounter rewriter_nopatch("rewriter_nopatch");
if (!ic) {
rewriter_nopatch.log();
return NULL;
}
return new Rewriter(ic->startRewrite(debug_name), num_args, ic->getLiveOuts());
}
RewriterVarUsage RewriterVarUsage::addUse() {
return RewriterVarUsage(var);
}
}
......@@ -16,130 +16,275 @@
#define PYSTON_ASMWRITING_REWRITER_H
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include "asm_writing/assembler.h"
#include "asm_writing/icinfo.h"
#include "core/ast.h"
namespace pyston {
class TypeRecorder;
class ICInfo;
class ICSlotInfo;
class ICSlotRewrite;
class ICInvalidator;
class Rewriter;
namespace assembler {
class Assembler;
class RewriterVar;
struct Location {
public:
enum LocationType : uint8_t {
Register,
XMMRegister,
Stack,
Scratch, // stack location, relative to the scratch start
// For representing constants that fit in 32-bits, that can be encoded as immediates
Constant,
AnyReg, // special type for use when specifying a location as a destination
None, // special type that represents the lack of a location, ex where a "ret void" gets returned
Uninitialized, // special type for an uninitialized (and invalid) location
};
public:
LocationType type;
union {
// only valid if type==Register; uses X86 numbering, not dwarf numbering.
// also valid if type==XMMRegister
int32_t regnum;
// only valid if type==Stack; this is the offset from bottom of the original frame.
// ie argument #6 will have a stack_offset of 0, #7 will have a stack offset of 8, etc
int32_t stack_offset;
// only valid if type == Scratch; offset from the beginning of the scratch area
int32_t scratch_offset;
// only valid if type==Constant
int32_t constant_val;
int32_t _data;
};
constexpr Location() : type(Uninitialized), _data(-1) {}
constexpr Location(const Location& r) : type(r.type), _data(r._data) {}
Location operator=(const Location& r) {
type = r.type;
_data = r._data;
return *this;
}
constexpr Location(LocationType type, int32_t data) : type(type), _data(data) {}
constexpr Location(assembler::Register reg) : type(Register), regnum(reg.regnum) {}
constexpr Location(assembler::XMMRegister reg) : type(XMMRegister), regnum(reg.regnum) {}
constexpr Location(assembler::GenericRegister reg)
: type(reg.type == assembler::GenericRegister::GP ? Register : reg.type == assembler::GenericRegister::XMM
? XMMRegister
: None),
regnum(reg.type == assembler::GenericRegister::GP ? reg.gp.regnum : reg.xmm.regnum) {}
assembler::Register asRegister() const;
assembler::XMMRegister asXMMRegister() const;
bool isClobberedByCall() const;
static constexpr Location any() { return Location(AnyReg, 0); }
static constexpr Location none() { return Location(None, 0); }
static Location forArg(int argnum);
bool operator==(const Location rhs) const { return this->asInt() == rhs.asInt(); }
bool operator!=(const Location rhs) const { return !(*this == rhs); }
uint64_t asInt() const { return (int)type + ((uint64_t)_data << 4); }
void dump() const;
};
static_assert(sizeof(Location) <= 8, "");
}
// TODO Maybe should make these classes abstract and then pick between a debug implementation
// and a release one, instead of trying to make one class do both?
namespace std {
template <> struct hash<pyston::Location> {
size_t operator()(const pyston::Location p) const { return p.asInt(); }
};
}
namespace pyston {
class RewriterVarUsage {
public:
enum KillFlag {
NoKill,
Kill,
};
class RewriterVar {
private:
Rewriter* rewriter;
int argnum;
int version;
RewriterVar* var;
bool done_using;
RewriterVarUsage();
RewriterVarUsage(const RewriterVarUsage&) = delete;
RewriterVarUsage& operator=(const RewriterVarUsage&) = delete;
void assertValid() {
assert(var);
assert(!done_using);
}
public:
RewriterVar() : rewriter(NULL), argnum(-100), version(-100) {}
RewriterVar(Rewriter* rewriter, int argnum, int version);
RewriterVar& operator=(const RewriterVar& rhs);
// Creates a new Usage object of this var; ownership of
// one use of the var gets passed to this new object.
RewriterVarUsage(RewriterVar* var);
// Move constructor; don't need it for performance reasons, but because
// semantically we have to pass the ownership of the use.
RewriterVarUsage(RewriterVarUsage&& usage);
RewriterVarUsage& operator=(RewriterVarUsage&& usage);
static RewriterVarUsage empty();
#ifndef NDEBUG
void assertValid() const;
void lock();
void unlock();
#else
inline void assertValid() const {}
inline void lock() {}
inline void unlock() {}
~RewriterVarUsage() {
if (!std::uncaught_exception())
assert(done_using);
}
#endif
int getArgnum();
void addGuard(intptr_t val);
void addGuardNotEq(intptr_t val);
// More efficient than getAttr().addGuard(), but less efficient than addGuard() if the value is already available:
void addAttrGuard(int offset, intptr_t val);
RewriterVar getAttr(int offset, int dest);
void incAttr(int offset);
void setAttr(int offset, const RewriterVar& val, bool user_visible = true);
RewriterVar move(int argnum);
bool isInReg();
void push();
RewriterVar cmp(AST_TYPE::AST_TYPE cmp_type, const RewriterVar& val, int dest);
RewriterVar toBool(int dest);
RewriterVar add(int64_t amount);
void setDoneUsing();
bool isDoneUsing();
void ensureDoneUsing();
RewriterVarUsage addUse();
void addGuard(uint64_t val);
void addGuardNotEq(uint64_t val);
void addAttrGuard(int offset, uint64_t val);
RewriterVarUsage getAttr(int offset, KillFlag kill, Location loc = Location::any());
void setAttr(int offset, RewriterVarUsage other);
RewriterVarUsage cmp(AST_TYPE::AST_TYPE cmp_type, RewriterVarUsage other, Location loc = Location::any());
RewriterVarUsage toBool(KillFlag kill, Location loc = Location::any());
friend class Rewriter;
};
class Rewriter;
// This might make more sense as an inner class of Rewriter, but
// you can't forward-declare that :/
class RewriterVar {
private:
Rewriter* rewriter;
int num_uses;
std::unordered_set<Location> locations;
bool isInLocation(Location l);
// Indicates that this value is a pointer to a fixed-size range in the scratch space.
// This is a vector of variable usages that keep the range allocated.
std::vector<RewriterVarUsage> scratch_range;
// Gets a copy of this variable in a register, spilling/reloading if necessary.
// TODO have to be careful with the result since the interface doesn't guarantee
// that the register will still contain your value when you go to use it
assembler::Register getInReg(Location l = Location::any());
assembler::XMMRegister getInXMMReg(Location l = Location::any());
// If this is an immediate, try getting it as one
assembler::Immediate tryGetAsImmediate(bool* is_immediate);
void dump();
public:
void incUse();
void decUse();
RewriterVar(Rewriter* rewriter, Location location) : rewriter(rewriter), num_uses(0) {
assert(rewriter);
locations.insert(location);
}
friend class RewriterVarUsage;
friend class Rewriter;
};
class Rewriter : public ICSlotRewrite::CommitHook {
private:
std::unique_ptr<ICSlotRewrite> rewrite;
assembler::Assembler* assembler;
const int num_orig_args;
const int num_temp_regs;
void finishAssembly(int continue_offset);
const Location return_location;
int alloca_bytes;
int max_pushes;
std::vector<int> pushes;
#ifndef NDEBUG
std::unordered_map<int, int> versions;
int next_version;
bool changed_something;
std::unordered_set<int> locked;
#endif
int ndecisions;
uint64_t decision_path;
bool done_guarding;
Rewriter(ICSlotRewrite* rewrite, int num_orig_args, int num_temp_regs);
std::vector<int> live_out_regs;
void addPush(int version);
std::unordered_map<Location, RewriterVar*> vars_by_location;
std::vector<RewriterVar*> args;
std::vector<RewriterVar*> live_outs;
public:
static Rewriter* createRewriter(void* ic_rtn_addr, int num_orig_args, int num_temp_regs, const char* debug_name);
Rewriter(ICSlotRewrite* rewrite, int num_args, const std::vector<int>& live_outs);
#ifndef NDEBUG
int mutate(int argnum);
void lock(int argnum);
void unlock(int argnum);
void checkVersion(int argnum, int version);
void checkArgsValid();
#else
inline int mutate(int argnum) { return 0; }
inline void lock(int argnum) {}
inline void unlock(int argnum) {}
inline void checkVersion(int argnum, int version) {}
inline void checkArgsValid() {}
#endif
void assertChangesOk() { assert(done_guarding); }
int getFuncStackSize() { return rewrite->getFuncStackSize(); }
int getScratchRbpOffset() { return rewrite->getScratchRbpOffset(); }
int getScratchBytes() { return rewrite->getScratchBytes(); }
RewriterVar getRbp();
RewriterVar getRsp();
void kill(RewriterVar* var);
void addDecision(int way);
// Allocates a register. dest must be of type Register or AnyReg
assembler::Register allocReg(Location dest);
// Allocates an 8-byte region in the scratch space
Location allocScratch();
assembler::Indirect indirectFor(Location l);
// Spills a specified register.
// If there are open callee-save registers, takes one of those, otherwise goes on the stack
void spillRegister(assembler::Register reg);
// Similar, but for XMM registers (always go on the stack)
void spillRegister(assembler::XMMRegister reg);
// Given an empty location, do the internal bookkeeping to create a new var out of that location.
RewriterVarUsage createNewVar(Location dest);
// Do the bookkeeping to say that var is now also in location l
void addLocationToVar(RewriterVar* var, Location l);
// Do the bookkeeping to say that var is no longer in location l
void removeLocationFromVar(RewriterVar* var, Location l);
void finishAssembly(int continue_offset) override;
RewriterVar alloca_(int bytes, int dest_argnum);
RewriterVar getArg(int argnum);
std::pair<RewriterVarUsage, int> _allocate(int n);
int ndecisions;
uint64_t decision_path;
public:
// This should be called exactly once for each argument
RewriterVarUsage getArg(int argnum);
Location getReturnDestination();
bool isDoneGuarding() { return done_guarding; }
void setDoneGuarding();
TypeRecorder* getTypeRecorder();
void trap();
void nop();
void annotate(int num);
RewriterVar pop(int argnum);
RewriterVar call(void* func_addr);
RewriterVar loadConst(int argnum, intptr_t val);
RewriterVarUsage loadConst(int64_t val, Location loc = Location::any());
RewriterVarUsage call(bool can_call_into_python, void* func_addr, std::vector<RewriterVarUsage> args);
RewriterVarUsage call(bool can_call_into_python, void* func_addr, RewriterVarUsage arg0);
RewriterVarUsage call(bool can_call_into_python, void* func_addr, RewriterVarUsage arg0, RewriterVarUsage arg1);
RewriterVarUsage allocate(int n);
RewriterVarUsage allocateAndCopy(RewriterVarUsage array, int n);
RewriterVarUsage allocateAndCopyPlus1(RewriterVarUsage first_elem, RewriterVarUsage rest, int n_rest);
void deallocateStack(int nbytes);
void addDependenceOn(ICInvalidator&);
void commit();
void commitReturning(RewriterVarUsage rtn);
void addDependenceOn(ICInvalidator&);
static Rewriter* createRewriter(void* rtn_addr, int num_args, const char* debug_name);
void addDecision(int way);
friend class RewriterVar;
friend class RewriterVarUsage;
};
}
......
// Copyright (c) 2014 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "asm_writing/rewriter2.h"
#include <vector>
#include "asm_writing/icinfo.h"
#include "core/common.h"
#include "core/stats.h"
namespace pyston {
static const assembler::Register allocatable_regs[] = {
assembler::RAX, assembler::RCX, assembler::RBX, assembler::RDX,
// no RSP
// no RBP
assembler::RDI, assembler::RSI, assembler::R8, assembler::R9, assembler::R10,
assembler::R11, assembler::R12, assembler::R13, assembler::R14, assembler::R15,
};
Location Location::forArg(int argnum) {
assert(argnum >= 0);
switch (argnum) {
case 0:
return assembler::RDI;
case 1:
return assembler::RSI;
case 2:
return assembler::RDX;
case 3:
return assembler::RCX;
case 4:
return assembler::R8;
case 5:
return assembler::R9;
default:
break;
}
RELEASE_ASSERT(0, "the following is untested");
// int offset = (argnum - 6) * 8;
// return Location(Stack, offset);
}
assembler::Register Location::asRegister() const {
assert(type == Register);
return assembler::Register(regnum);
}
assembler::XMMRegister Location::asXMMRegister() const {
assert(type == XMMRegister);
return assembler::XMMRegister(regnum);
}
bool Location::isClobberedByCall() const {
if (type == Register) {
return !asRegister().isCalleeSave();
}
if (type == XMMRegister)
return true;
if (type == Scratch)
return false;
if (type == Constant)
return false;
RELEASE_ASSERT(0, "%d", type);
}
void Location::dump() const {
if (type == Register) {
asRegister().dump();
return;
}
if (type == XMMRegister) {
printf("%%xmm%d\n", regnum);
return;
}
if (type == Scratch) {
printf("scratch(%d)\n", stack_offset);
return;
}
if (type == Constant) {
printf("imm(%d)\n", constant_val);
return;
}
RELEASE_ASSERT(0, "%d", type);
}
RewriterVarUsage2::RewriterVarUsage2(RewriterVar2* var) : var(var), done_using(false) {
assert(var->rewriter);
}
void RewriterVarUsage2::addAttrGuard(int offset, uint64_t val) {
Rewriter2* rewriter = var->rewriter;
assembler::Assembler* assembler = rewriter->assembler;
assert(!rewriter->done_guarding && "too late to add a guard!");
assertValid();
assembler::Register this_reg = var->getInReg();
if (val < (-1L << 31) || val >= (1L << 31) - 1) {
assembler::Register reg = rewriter->allocReg(Location::any());
assembler->mov(assembler::Immediate(val), reg);
assembler->cmp(assembler::Indirect(this_reg, offset), reg);
} else {
assembler->cmp(assembler::Indirect(this_reg, offset), assembler::Immediate(val));
}
assembler->jne(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
}
RewriterVarUsage2 RewriterVarUsage2::getAttr(int offset, KillFlag kill, Location dest) {
assertValid();
// Save these, since if we kill this register the var might disappear:
assembler::Register this_reg = var->getInReg();
Rewriter2* rewriter = var->rewriter;
if (kill == Kill) {
setDoneUsing();
}
assembler::Register newvar_reg = rewriter->allocReg(dest);
RewriterVarUsage2 newvar = rewriter->createNewVar(newvar_reg);
rewriter->assembler->mov(assembler::Indirect(this_reg, offset), newvar_reg);
return std::move(newvar);
}
void RewriterVarUsage2::setAttr(int offset, RewriterVarUsage2 val) {
assertValid();
var->rewriter->assertChangesOk();
assembler::Register this_reg = var->getInReg();
bool is_immediate;
assembler::Immediate imm = val.var->tryGetAsImmediate(&is_immediate);
if (is_immediate) {
var->rewriter->assembler->movq(imm, assembler::Indirect(this_reg, offset));
} else {
assembler::Register other_reg = val.var->getInReg();
// TODO the allocator could choose to spill this_reg in order to load other_reg...
// Hopefuly it won't make that decision, so we should just be able to guard on it for now:
assert(this_reg != other_reg);
var->rewriter->assembler->mov(other_reg, assembler::Indirect(this_reg, offset));
}
val.setDoneUsing();
}
void RewriterVarUsage2::setDoneUsing() {
assertValid();
done_using = true;
var->decUse();
var = NULL;
}
RewriterVarUsage2::RewriterVarUsage2(RewriterVarUsage2&& usage) {
assert(!usage.done_using);
assert(usage.var != NULL);
var = usage.var;
done_using = usage.done_using;
usage.var = NULL;
usage.done_using = true;
}
RewriterVarUsage2& RewriterVarUsage2::operator=(RewriterVarUsage2&& usage) {
assert(done_using);
assert(var == NULL);
assert(!usage.done_using);
assert(usage.var != NULL);
var = usage.var;
done_using = usage.done_using;
usage.var = NULL;
usage.done_using = true;
return *this;
}
void RewriterVar2::dump() {
printf("RewriterVar2 at %p: %d uses. %ld locations:\n", this, num_uses, locations.size());
for (Location l : locations)
l.dump();
}
assembler::Immediate RewriterVar2::tryGetAsImmediate(bool* is_immediate) {
for (Location l : locations) {
if (l.type == Location::Constant) {
*is_immediate = true;
return assembler::Immediate(l.constant_val);
}
}
*is_immediate = false;
return assembler::Immediate((uint64_t)0);
}
assembler::Register RewriterVar2::getInReg(Location dest) {
assert(dest.type == Location::Register || dest.type == Location::AnyReg);
// assembler::Register reg = var->rewriter->allocReg(l);
// var->rewriter->addLocationToVar(var, reg);
// return reg;
assert(locations.size());
#ifndef NDEBUG
for (Location l : locations) {
ASSERT(l.type != Location::Constant, "why do you want this in a register?");
}
#endif
// Not sure if this is worth it,
// but first try to see if we're already in this specific register
for (Location l : locations) {
if (l == dest)
return l.asRegister();
}
// Then, see if we're in another register
for (Location l : locations) {
if (l.type == Location::Register) {
assembler::Register reg = l.asRegister();
if (dest.type != Location::AnyReg) {
assembler::Register dest_reg = dest.asRegister();
assert(dest_reg != reg); // should have been caught by the previous case
rewriter->assembler->mov(reg, dest_reg);
rewriter->addLocationToVar(this, dest_reg);
return dest_reg;
}
return reg;
}
}
assert(locations.size() == 1);
Location l(*locations.begin());
assert(l.type == Location::Scratch);
assembler::Register reg = rewriter->allocReg(dest);
assert(rewriter->vars_by_location.count(reg) == 0);
assembler::Indirect mem = rewriter->indirectFor(l);
rewriter->assembler->mov(mem, reg);
rewriter->addLocationToVar(this, reg);
return reg;
}
assembler::XMMRegister RewriterVar2::getInXMMReg(Location dest) {
assert(dest.type == Location::XMMRegister || dest.type == Location::AnyReg);
assert(locations.size());
#ifndef NDEBUG
for (Location l : locations) {
ASSERT(l.type != Location::Constant, "why do you want this in a register?");
}
#endif
// Not sure if this is worth it,
// but first try to see if we're already in this specific register
for (Location l : locations) {
if (l == dest)
return l.asXMMRegister();
}
// Then, see if we're in another register
for (Location l : locations) {
if (l.type == Location::XMMRegister) {
assembler::XMMRegister reg = l.asXMMRegister();
if (dest.type != Location::AnyReg) {
assembler::XMMRegister dest_reg = dest.asXMMRegister();
assert(dest_reg != reg); // should have been caught by the previous case
rewriter->assembler->movsd(reg, dest_reg);
rewriter->addLocationToVar(this, dest_reg);
return dest_reg;
}
return reg;
}
}
assert(locations.size() == 1);
Location l(*locations.begin());
assert(l.type == Location::Scratch);
assert(dest.type == Location::XMMRegister);
assembler::XMMRegister reg = dest.asXMMRegister();
assert(rewriter->vars_by_location.count(reg) == 0);
assembler::Indirect mem = rewriter->indirectFor(l);
rewriter->assembler->movsd(mem, reg);
rewriter->addLocationToVar(this, reg);
return reg;
}
RewriterVarUsage2::RewriterVarUsage2() : var(NULL), done_using(true) {
}
RewriterVarUsage2 RewriterVarUsage2::empty() {
return RewriterVarUsage2();
}
void RewriterVar2::decUse() {
num_uses--;
if (num_uses == 0) {
rewriter->kill(this);
delete this;
}
}
void RewriterVar2::incUse() {
num_uses++;
}
bool RewriterVar2::isInLocation(Location l) {
return locations.count(l) != 0;
}
void Rewriter2::setDoneGuarding() {
assert(!done_guarding);
done_guarding = true;
for (RewriterVar2* var : args) {
var->decUse();
}
args.clear();
}
RewriterVarUsage2 Rewriter2::getArg(int argnum) {
assert(!done_guarding);
assert(argnum >= 0 && argnum < args.size());
RewriterVar2* var = args[argnum];
var->incUse();
return RewriterVarUsage2(var);
}
Location Rewriter2::getReturnDestination() {
return return_location;
}
void Rewriter2::trap() {
assembler->trap();
}
RewriterVarUsage2 Rewriter2::loadConst(int64_t val, Location dest) {
if (val >= (-1L << 31) && val < (1L << 31) - 1) {
Location l(Location::Constant, val);
return createNewVar(l);
}
assembler::Register reg = allocReg(dest);
RewriterVarUsage2 var = createNewVar(reg);
assembler->mov(assembler::Immediate(val), reg);
// I guess you don't need std::move here:
return var;
}
RewriterVarUsage2 Rewriter2::call(bool can_call_into_python, void* func_addr, RewriterVarUsage2 arg0) {
std::vector<RewriterVarUsage2> args;
args.push_back(std::move(arg0));
return call(can_call_into_python, func_addr, std::move(args));
}
RewriterVarUsage2 Rewriter2::call(bool can_call_into_python, void* func_addr, RewriterVarUsage2 arg0,
RewriterVarUsage2 arg1) {
std::vector<RewriterVarUsage2> args;
args.push_back(std::move(arg0));
args.push_back(std::move(arg1));
return call(can_call_into_python, func_addr, std::move(args));
}
static const Location caller_save_registers[]{
assembler::RAX, assembler::RCX, assembler::RDX, assembler::RSI, assembler::RDI,
assembler::R8, assembler::R9, assembler::R10, assembler::R11, assembler::XMM0,
assembler::XMM1, assembler::XMM2, assembler::XMM3, assembler::XMM4, assembler::XMM5,
assembler::XMM6, assembler::XMM7, assembler::XMM8, assembler::XMM9, assembler::XMM10,
assembler::XMM11, assembler::XMM12, assembler::XMM13, assembler::XMM14, assembler::XMM15,
};
RewriterVarUsage2 Rewriter2::call(bool can_call_into_python, void* func_addr, std::vector<RewriterVarUsage2> args) {
assert(!can_call_into_python);
assertChangesOk();
// RewriterVarUsage2 scratch = createNewVar(Location::any());
assembler::Register r = allocReg(assembler::R11);
for (int i = 0; i < args.size(); i++) {
Location l(Location::forArg(i));
RewriterVar2* var = args[i].var;
// printf("%d ", i);
// var->dump();
if (!var->isInLocation(l)) {
assembler::Register r = l.asRegister();
{
// this forces the register allocator to spill this register:
assembler::Register r2 = allocReg(l);
assert(r == r2);
assert(vars_by_location.count(l) == 0);
}
// FIXME: get rid of tryGetAsImmediate
// instead do that work here; ex this could be a stack location
bool is_immediate;
assembler::Immediate imm = var->tryGetAsImmediate(&is_immediate);
if (is_immediate) {
assembler->mov(imm, r);
addLocationToVar(var, l);
} else {
assembler::Register r2 = var->getInReg(l);
assert(var->locations.count(r2));
assert(r2 == r);
}
}
assert(var->isInLocation(Location::forArg(i)));
}
#ifndef NDEBUG
for (int i = 0; i < args.size(); i++) {
RewriterVar2* var = args[i].var;
if (!var->isInLocation(Location::forArg(i))) {
var->dump();
}
assert(var->isInLocation(Location::forArg(i)));
}
#endif
// This is kind of hacky: we release the use of these right now,
// and then expect that everything else will not clobber any of the arguments.
// Naively moving this below the reg spilling will always spill the arguments;
// but sometimes you need to do that if the argument lives past the call.
// Hacky, but the right way to do it requires a bit of reworking so that it can
// spill but keep its current use.
for (int i = 0; i < args.size(); i++) {
args[i].setDoneUsing();
}
// Spill caller-saved registers:
for (auto check_reg : caller_save_registers) {
// check_reg.dump();
assert(check_reg.isClobberedByCall());
auto it = vars_by_location.find(check_reg);
if (it == vars_by_location.end())
continue;
RewriterVar2* var = it->second;
bool need_to_spill = true;
for (Location l : var->locations) {
if (!l.isClobberedByCall()) {
need_to_spill = false;
break;
}
}
if (need_to_spill) {
if (check_reg.type == Location::Register) {
spillRegister(check_reg.asRegister());
} else {
assert(check_reg.type == Location::XMMRegister);
assert(var->locations.size() == 1);
spillRegister(check_reg.asXMMRegister());
}
} else {
removeLocationFromVar(var, check_reg);
}
}
#ifndef NDEBUG
for (const auto& p : vars_by_location) {
Location l = p.first;
// l.dump();
if (l.isClobberedByCall()) {
p.second->dump();
}
assert(!l.isClobberedByCall());
}
#endif
assembler->mov(assembler::Immediate(func_addr), r);
assembler->callq(r);
assert(vars_by_location.count(assembler::RAX) == 0);
RewriterVar2* var = vars_by_location[assembler::RAX] = new RewriterVar2(this, assembler::RAX);
return RewriterVarUsage2(var);
}
void Rewriter2::commit() {
static StatCounter rewriter2_commits("rewriter2_commits");
rewriter2_commits.log();
assert(done_guarding && "Could call setDoneGuarding for you, but probably best to do it yourself");
// if (!done_guarding)
// setDoneGuarding();
assert(live_out_regs.size() == live_outs.size());
for (int i = 0; i < live_outs.size(); i++) {
assembler::GenericRegister ru = assembler::GenericRegister::fromDwarf(live_out_regs[i]);
Location expected(ru);
RewriterVar2* var = live_outs[i];
// for (Location l : var->locations) {
// printf("%d %d\n", l.type, l._data);
//}
if (!var->isInLocation(expected)) {
assert(vars_by_location.count(expected) == 0);
if (ru.type == assembler::GenericRegister::GP) {
assembler::Register reg = var->getInReg(ru.gp);
assert(reg == ru.gp);
} else if (ru.type == assembler::GenericRegister::XMM) {
assembler::XMMRegister reg = var->getInXMMReg(ru.xmm);
assert(reg == ru.xmm);
} else {
RELEASE_ASSERT(0, "%d", ru.type);
}
}
assert(var->isInLocation(ru));
var->decUse();
}
assert(vars_by_location.size() == 0);
rewrite->commit(0, this);
}
void Rewriter2::finishAssembly(int continue_offset) {
assembler->jmp(assembler::JumpDestination::fromStart(continue_offset));
assembler->fillWithNops();
}
void Rewriter2::commitReturning(RewriterVarUsage2 usage) {
assert(usage.var->isInLocation(getReturnDestination()));
/*
Location l = usage.var->location;
Location expected = getReturnDestination();
if (l != expected) {
assert(l.type == Location::Register);
assert(expected.type == Location::Register);
assembler->mov(l.asRegister(), expected.asRegister());
}
*/
usage.setDoneUsing();
commit();
}
void Rewriter2::addDependenceOn(ICInvalidator& invalidator) {
rewrite->addDependenceOn(invalidator);
}
void Rewriter2::kill(RewriterVar2* var) {
for (Location l : var->locations) {
assert(vars_by_location[l] == var);
vars_by_location.erase(l);
}
}
Location Rewriter2::allocScratch() {
int scratch_bytes = rewrite->getScratchBytes();
for (int i = 0; i < scratch_bytes; i += 8) {
Location l(Location::Scratch, i);
if (vars_by_location.count(l) == 0)
return l;
}
RELEASE_ASSERT(0, "Using all %d bytes of scratch!", scratch_bytes);
}
assembler::Indirect Rewriter2::indirectFor(Location l) {
assert(l.type == Location::Scratch);
// TODO it can sometimes be more efficient to do RSP-relative addressing?
int rbp_offset = rewrite->getScratchRbpOffset() + l.scratch_offset;
return assembler::Indirect(assembler::RBP, rbp_offset);
}
void Rewriter2::spillRegister(assembler::Register reg) {
assert(done_guarding);
RewriterVar2* var = vars_by_location[reg];
assert(var);
// First, try to spill into a callee-save register:
for (assembler::Register new_reg : allocatable_regs) {
if (!new_reg.isCalleeSave())
continue;
if (vars_by_location.count(new_reg))
continue;
assembler->mov(reg, new_reg);
addLocationToVar(var, new_reg);
removeLocationFromVar(var, reg);
return;
}
Location scratch = allocScratch();
assembler::Indirect mem = indirectFor(scratch);
assembler->mov(reg, mem);
addLocationToVar(var, scratch);
removeLocationFromVar(var, reg);
}
void Rewriter2::spillRegister(assembler::XMMRegister reg) {
assert(done_guarding);
RewriterVar2* var = vars_by_location[reg];
assert(var);
assert(var->locations.size() == 1);
Location scratch = allocScratch();
assembler::Indirect mem = indirectFor(scratch);
assembler->movsd(reg, mem);
addLocationToVar(var, scratch);
removeLocationFromVar(var, reg);
}
assembler::Register Rewriter2::allocReg(Location dest) {
if (dest.type == Location::AnyReg) {
for (assembler::Register reg : allocatable_regs) {
if (vars_by_location.count(reg) == 0)
return reg;
}
RELEASE_ASSERT(0, "couldn't find a reg to allocate and haven't added spilling");
} else if (dest.type == Location::Register) {
assembler::Register reg(dest.regnum);
if (vars_by_location.count(reg)) {
spillRegister(reg);
}
assert(vars_by_location.count(reg) == 0);
return reg;
} else {
RELEASE_ASSERT(0, "%d", dest.type);
}
}
void Rewriter2::addLocationToVar(RewriterVar2* var, Location l) {
assert(!var->isInLocation(l));
assert(vars_by_location.count(l) == 0);
ASSERT(l.type == Location::Register || l.type == Location::XMMRegister || l.type == Location::Scratch, "%d",
l.type);
var->locations.insert(l);
vars_by_location[l] = var;
}
void Rewriter2::removeLocationFromVar(RewriterVar2* var, Location l) {
assert(var->isInLocation(l));
assert(vars_by_location[l] = var);
vars_by_location.erase(l);
var->locations.erase(l);
}
RewriterVarUsage2 Rewriter2::createNewVar(Location dest) {
RewriterVar2*& var = vars_by_location[dest];
assert(!var);
var = new RewriterVar2(this, dest);
return var;
}
TypeRecorder* Rewriter2::getTypeRecorder() {
return rewrite->getTypeRecorder();
}
Rewriter2::Rewriter2(ICSlotRewrite* rewrite, int num_args, const std::vector<int>& live_outs)
: rewrite(rewrite), assembler(rewrite->getAssembler()), return_location(rewrite->returnRegister()),
done_guarding(false) {
// assembler->trap();
for (int i = 0; i < num_args; i++) {
Location l = Location::forArg(i);
RewriterVar2* var = new RewriterVar2(this, l);
vars_by_location[l] = var;
args.push_back(var);
}
static StatCounter rewriter_starts("rewriter2_starts");
rewriter_starts.log();
static StatCounter rewriter_spillsavoided("rewriter2_spillsavoided");
// Calculate the list of live-ins based off the live-outs list,
// and create a Use of them so that they get preserved
for (int dwarf_regnum : live_outs) {
assembler::GenericRegister ru = assembler::GenericRegister::fromDwarf(dwarf_regnum);
Location l(ru);
// We could handle this here, but for now we're assuming that the return destination
// will get removed from this list before it gets handed to us.
assert(l != getReturnDestination());
//// The return register is the only live-out that is not also a live-in.
// if (l == getReturnDestination()) {
// l.dump();
// continue;
//}
if (l.isClobberedByCall()) {
rewriter_spillsavoided.log();
}
RewriterVar2*& var = vars_by_location[l];
if (var) {
var->incUse();
} else {
var = new RewriterVar2(this, l);
}
this->live_outs.push_back(var);
this->live_out_regs.push_back(dwarf_regnum);
}
}
Rewriter2* Rewriter2::createRewriter(void* rtn_addr, int num_args, const char* debug_name) {
ICInfo* ic = getICInfo(rtn_addr);
static StatCounter rewriter_nopatch("rewriter_nopatch");
if (!ic) {
rewriter_nopatch.log();
return NULL;
}
return new Rewriter2(ic->startRewrite(debug_name), num_args, ic->getLiveOuts());
}
}
// Copyright (c) 2014 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PYSTON_ASMWRITING_REWRITER2_H
#define PYSTON_ASMWRITING_REWRITER2_H
#include <memory>
#include "asm_writing/assembler.h"
#include "asm_writing/icinfo.h"
namespace pyston {
class TypeRecorder;
class ICInfo;
class ICSlotInfo;
class ICSlotRewrite;
class ICInvalidator;
class RewriterVar2;
struct Location {
public:
enum LocationType : uint8_t {
Register,
XMMRegister,
// Stack,
Scratch, // stack location, relative to the scratch start
// For representing constants that fit in 32-bits, that can be encoded as immediates
Constant,
AnyReg, // special type for use when specifying a location as a destination
None, // special type that represents the lack of a location, ex where a "ret void" gets returned
Uninitialized, // special type for an uninitialized (and invalid) location
};
public:
LocationType type;
union {
// only valid if type==Register; uses X86 numbering, not dwarf numbering.
// also valid if type==XMMRegister
int32_t regnum;
// only valid if type==Stack; this is the offset from bottom of the original frame.
// ie argument #6 will have a stack_offset of 0, #7 will have a stack offset of 8, etc
int32_t stack_offset;
// only valid if type == Scratch; offset from the beginning of the scratch area
int32_t scratch_offset;
// only valid if type==Constant
int32_t constant_val;
int32_t _data;
};
constexpr Location() : type(Uninitialized), _data(-1) {}
constexpr Location(const Location& r) : type(r.type), _data(r._data) {}
Location operator=(const Location& r) {
type = r.type;
_data = r._data;
return *this;
}
constexpr Location(LocationType type, int32_t data) : type(type), _data(data) {}
constexpr Location(assembler::Register reg) : type(Register), regnum(reg.regnum) {}
constexpr Location(assembler::XMMRegister reg) : type(XMMRegister), regnum(reg.regnum) {}
constexpr Location(assembler::GenericRegister reg)
: type(reg.type == assembler::GenericRegister::GP ? Register : reg.type == assembler::GenericRegister::XMM
? XMMRegister
: None),
regnum(reg.type == assembler::GenericRegister::GP ? reg.gp.regnum : reg.xmm.regnum) {}
assembler::Register asRegister() const;
assembler::XMMRegister asXMMRegister() const;
bool isClobberedByCall() const;
static constexpr Location any() { return Location(AnyReg, 0); }
static constexpr Location none() { return Location(None, 0); }
static Location forArg(int argnum);
bool operator==(const Location rhs) const { return this->asInt() == rhs.asInt(); }
bool operator!=(const Location rhs) const { return !(*this == rhs); }
uint64_t asInt() const { return (int)type + ((uint64_t)_data << 4); }
void dump() const;
};
static_assert(sizeof(Location) <= 8, "");
}
namespace std {
template <> struct hash<pyston::Location> {
size_t operator()(const pyston::Location p) const { return p.asInt(); }
};
}
namespace pyston {
class RewriterVarUsage2 {
public:
enum KillFlag {
NoKill,
Kill,
};
private:
RewriterVar2* var;
bool done_using;
RewriterVarUsage2();
RewriterVarUsage2(const RewriterVarUsage2&) = delete;
RewriterVarUsage2& operator=(const RewriterVarUsage2&) = delete;
void assertValid() {
assert(var);
assert(!done_using);
}
public:
// Creates a new Usage object of this var; ownership of
// one use of the var gets passed to this new object.
RewriterVarUsage2(RewriterVar2* var);
// Move constructor; don't need it for performance reasons, but because
// semantically we have to pass the ownership of the use.
RewriterVarUsage2(RewriterVarUsage2&& usage);
RewriterVarUsage2& operator=(RewriterVarUsage2&& usage);
static RewriterVarUsage2 empty();
#ifndef NDEBUG
~RewriterVarUsage2() {
if (!std::uncaught_exception())
assert(done_using);
}
#endif
void setDoneUsing();
// RewriterVarUsage2 addUse() { return var->addUse(); }
RewriterVarUsage2 addUse();
void addAttrGuard(int offset, uint64_t val);
RewriterVarUsage2 getAttr(int offset, KillFlag kill, Location loc = Location::any());
void setAttr(int offset, RewriterVarUsage2 other);
friend class Rewriter2;
};
class Rewriter2;
// This might make more sense as an inner class of Rewriter2, but
// you can't forward-declare that :/
class RewriterVar2 {
private:
Rewriter2* rewriter;
int num_uses;
std::unordered_set<Location> locations;
bool isInLocation(Location l);
// Gets a copy of this variable in a register, spilling/reloading if necessary.
// TODO have to be careful with the result since the interface doesn't guarantee
// that the register will still contain your value when you go to use it
assembler::Register getInReg(Location l = Location::any());
assembler::XMMRegister getInXMMReg(Location l = Location::any());
// If this is an immediate, try getting it as one
assembler::Immediate tryGetAsImmediate(bool* is_immediate);
void dump();
public:
void incUse();
void decUse();
RewriterVar2(Rewriter2* rewriter, Location location) : rewriter(rewriter), num_uses(1) {
assert(rewriter);
locations.insert(location);
}
friend class RewriterVarUsage2;
friend class Rewriter2;
};
class Rewriter2 : public ICSlotRewrite::CommitHook {
private:
std::unique_ptr<ICSlotRewrite> rewrite;
assembler::Assembler* assembler;
const Location return_location;
bool done_guarding;
std::vector<int> live_out_regs;
std::unordered_map<Location, RewriterVar2*> vars_by_location;
std::vector<RewriterVar2*> args;
std::vector<RewriterVar2*> live_outs;
Rewriter2(ICSlotRewrite* rewrite, int num_args, const std::vector<int>& live_outs);
void assertChangesOk() { assert(done_guarding); }
void kill(RewriterVar2* var);
// Allocates a register. dest must be of type Register or AnyReg
assembler::Register allocReg(Location dest);
// Allocates an 8-byte region in the scratch space
Location allocScratch();
assembler::Indirect indirectFor(Location l);
// Spills a specified register.
// If there are open callee-save registers, takes one of those, otherwise goes on the stack
void spillRegister(assembler::Register reg);
// Similar, but for XMM registers (always go on the stack)
void spillRegister(assembler::XMMRegister reg);
// Given an empty location, do the internal bookkeeping to create a new var out of that location.
RewriterVarUsage2 createNewVar(Location dest);
// Do the bookkeeping to say that var is now also in location l
void addLocationToVar(RewriterVar2* var, Location l);
// Do the bookkeeping to say that var is no longer in location l
void removeLocationFromVar(RewriterVar2* var, Location l);
void finishAssembly(int continue_offset) override;
public:
// This should be called exactly once for each argument
RewriterVarUsage2 getArg(int argnum);
Location getReturnDestination();
bool isDoneGuarding() { return done_guarding; }
void setDoneGuarding();
TypeRecorder* getTypeRecorder();
void trap();
RewriterVarUsage2 loadConst(int64_t val, Location loc = Location::any());
RewriterVarUsage2 call(bool can_call_into_python, void* func_addr, std::vector<RewriterVarUsage2> args);
RewriterVarUsage2 call(bool can_call_into_python, void* func_addr, RewriterVarUsage2 arg0);
RewriterVarUsage2 call(bool can_call_into_python, void* func_addr, RewriterVarUsage2 arg0, RewriterVarUsage2 arg1);
void commit();
void commitReturning(RewriterVarUsage2 rtn);
void addDependenceOn(ICInvalidator&);
static Rewriter2* createRewriter(void* rtn_addr, int num_args, const char* debug_name);
friend class RewriterVar2;
friend class RewriterVarUsage2;
};
}
#endif
......@@ -1812,7 +1812,7 @@ CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) {
if (source->ast->type == AST_TYPE::ClassDef) {
// A classdef always starts with "__module__ = __name__"
Box* module_name = source->parent_module->getattr("__name__", NULL, NULL);
Box* module_name = source->parent_module->getattr("__name__", NULL);
assert(module_name->cls == str_cls);
AST_Assign* module_assign = new AST_Assign();
module_assign->targets.push_back(makeName("__module__", AST_TYPE::Store));
......
......@@ -54,6 +54,11 @@ struct ArgPassSpec {
int totalPassed() { return num_args + num_keywords + (has_starargs ? 1 : 0) + (has_kwargs ? 1 : 0); }
uintptr_t asInt() const { return *reinterpret_cast<const uintptr_t*>(this); }
void dump() {
printf("(has_starargs=%s, has_kwargs=%s, num_keywords=%d, num_args=%d)\n", has_starargs ? "true" : "false",
has_kwargs ? "true" : "false", num_keywords, num_args);
}
};
static_assert(sizeof(ArgPassSpec) <= sizeof(void*), "ArgPassSpec doesn't fit in register!");
......@@ -371,10 +376,9 @@ private:
};
class SetattrRewriteArgs2;
class SetattrRewriteArgs;
class GetattrRewriteArgs;
class GetattrRewriteArgs2;
class DelattrRewriteArgs2;
class DelattrRewriteArgs;
struct HCAttrs {
public:
......@@ -398,15 +402,15 @@ public:
HCAttrs* getAttrsPtr();
void setattr(const std::string& attr, Box* val, SetattrRewriteArgs2* rewrite_args2);
void setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite_args);
void giveAttr(const std::string& attr, Box* val) {
assert(this->getattr(attr) == NULL);
this->setattr(attr, val, NULL);
}
Box* getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args, GetattrRewriteArgs2* rewrite_args2);
Box* getattr(const std::string& attr) { return getattr(attr, NULL, NULL); }
void delattr(const std::string& attr, DelattrRewriteArgs2* rewrite_args);
Box* getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args);
Box* getattr(const std::string& attr) { return getattr(attr, NULL); }
void delattr(const std::string& attr, DelattrRewriteArgs* rewrite_args);
};
......
......@@ -56,7 +56,7 @@ extern "C" Box* dir(Box* obj) {
}
// If __dict__ is present use its keys and add the reset below
Box* obj_dict = getattr_internal(obj, "__dict__", false, true, nullptr, nullptr);
Box* obj_dict = getattr_internal(obj, "__dict__", false, true, nullptr);
if (obj_dict && obj_dict->cls == dict_cls) {
result = new BoxedList();
for (auto& kv : static_cast<BoxedDict*>(obj_dict)->d) {
......@@ -332,7 +332,7 @@ Box* getattrFunc(Box* obj, Box* _str, Box* default_value) {
}
BoxedString* str = static_cast<BoxedString*>(_str);
Box* rtn = getattr_internal(obj, str->s, true, true, NULL, NULL);
Box* rtn = getattr_internal(obj, str->s, true, true, NULL);
if (!rtn) {
if (default_value)
......@@ -351,7 +351,7 @@ Box* hasattr(Box* obj, Box* _str) {
}
BoxedString* str = static_cast<BoxedString*>(_str);
Box* attr = getattr_internal(obj, str->s, true, true, NULL, NULL);
Box* attr = getattr_internal(obj, str->s, true, true, NULL);
Box* rtn = attr ? True : False;
return rtn;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -84,10 +84,10 @@ extern "C" BoxedClosure* createClosure(BoxedClosure* parent_closure);
class BinopRewriteArgs;
extern "C" Box* binopInternal(Box* lhs, Box* rhs, int op_type, bool inplace, BinopRewriteArgs* rewrite_args);
class CallRewriteArgs;
Box* typeCallInternal(BoxedFunction* f, CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Box* arg1, Box* arg2,
Box* arg3, Box** args, const std::vector<const std::string*>* keyword_names);
class CallRewriteArgs;
enum LookupScope {
CLASS_ONLY = 1,
INST_ONLY = 2,
......@@ -97,14 +97,13 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope,
ArgPassSpec argspec, Box* arg1, Box* arg2, Box* arg3, Box** args,
const std::vector<const std::string*>* keyword_names);
extern "C" void delattr_internal(Box* obj, const std::string& attr, bool allow_custom,
DelattrRewriteArgs2* rewrite_args);
DelattrRewriteArgs* rewrite_args);
struct CompareRewriteArgs;
Box* compareInternal(Box* lhs, Box* rhs, int op_type, CompareRewriteArgs* rewrite_args);
Box* getattr_internal(Box* obj, const std::string& attr, bool check_cls, bool allow_custom,
GetattrRewriteArgs* rewrite_args, GetattrRewriteArgs2* rewrite_args2);
GetattrRewriteArgs* rewrite_args);
Box* typeLookup(BoxedClass* cls, const std::string& attr, GetattrRewriteArgs* rewrite_args,
GetattrRewriteArgs2* rewrite_args2);
Box* typeLookup(BoxedClass* cls, const std::string& attr, GetattrRewriteArgs* rewrite_args);
extern "C" void raiseAttributeErrorStr(const char* typeName, const char* attr) __attribute__((__noreturn__));
extern "C" void raiseAttributeError(Box* obj, const char* attr) __attribute__((__noreturn__));
......
......@@ -84,7 +84,7 @@ extern "C" BoxedFunction::BoxedFunction(CLFunction* f)
// this->giveAttr("__name__", boxString(&f->source->ast->name));
this->giveAttr("__name__", boxString(f->source->getName()));
Box* modname = f->source->parent_module->getattr("__name__", NULL, NULL);
Box* modname = f->source->parent_module->getattr("__name__", NULL);
this->giveAttr("__module__", modname);
}
......@@ -110,7 +110,7 @@ extern "C" BoxedFunction::BoxedFunction(CLFunction* f, std::initializer_list<Box
// this->giveAttr("__name__", boxString(&f->source->ast->name));
this->giveAttr("__name__", boxString(f->source->getName()));
Box* modname = f->source->parent_module->getattr("__name__", NULL, NULL);
Box* modname = f->source->parent_module->getattr("__name__", NULL);
this->giveAttr("__module__", modname);
}
......@@ -516,7 +516,7 @@ Box* objectNew(BoxedClass* cls, BoxedTuple* args) {
if (args->elts.size() != 0) {
// TODO slow
if (typeLookup(cls, "__init__", NULL, NULL) == typeLookup(object_cls, "__init__", NULL, NULL))
if (typeLookup(cls, "__init__", NULL) == typeLookup(object_cls, "__init__", NULL))
raiseExcHelper(TypeError, "object.__new__() takes no parameters");
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment