Files
avmplus/halfmoon/hm-interpreter.cpp
dmcallis 65a0592776 Initial source code drop
Source code drop for the Actionscript virtual machine in the Adobe
Flash Player for December 2013
2013-12-02 16:04:04 -08:00

563 lines
19 KiB
C++

/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* vi: set ts=2 sw=2 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "hm-main.h"
#ifdef VMCFG_HALFMOON
namespace halfmoon {
using namespace avmplus;
// Interpreter concept.
//
// We interpret the instr graph directly without a schedule. Instructions
// are visited in order within blocks; the code must have previously been
// scheduled into some legal order (defs dominate uses, etc).
//
// We need a place for a value for each result of each instr, not just one
// per instr. A single result per instr would be simpler. We will have this
// problem for translation to LIR too. We could precompute the size & layout
// of this array once.
//
// option 1 - (rejected) walk the IR directly. Rejected because the data
// nodes must be evaluated in some sequential order, because we are on a
// sequential computer. Whether we record the order (using a scheduler) or
// compute it on the fly in an iterator, it is scheduling. So, require a
// schedule.
//
// option 2 - (rejected) compute DFS of all instructions in linear list.
// Rejected because computing the DFS order amounts to scheduling.
//
// option 3 - schedule, then walk the cfg.
//
// Any legal schedule is fine. Need a mapping of instr to block,
// which we compute during scheduling (easy). Since the schedule places
// labels, gotos, statements, expressions, and branches in the right order, we
// just evaluate instructions when visited and store results.
//
// Since any schedule will work, and since any other output phase would have
// to schedule, this is a way of checking schedules.
InterpreterData::InterpreterData(Allocator& alloc, InstrGraph* ir)
: max_argc_(0), ir_(ir) {
Allocator0 alloc0(alloc);
for (AllInstrRange i(ir); !i.empty(); i.popFront()) {
Instr* instr = i.front();
int argc = numUses(instr);
max_argc_ = argc > max_argc_ ? argc : max_argc_;
}
}
bool checkValues(Interpreter& frame, Instr* instr) {
for (ArrayRange<Def> r = defRange(instr); !r.empty(); r.popFront())
if (r.front().isUsed())
frame.get(&r.front());
return true;
}
void printInstr(Interpreter* frame, Instr* instr) {
if (enable_trace) {
if (isBlockStart(instr))
return;
frame->console() << "X ";
printInstr(frame->console(), instr);
}
}
/**
* Run the CFG interpreter for the given method. This implements the
* same signature as all AS3 methods but returns a Val instead of a word
* or double. It is called from each of the ABI-specific stubs.
*/
Value Interpreter::stub(MethodEnv* env, int32_t argc, uint32_t* args) {
MMgc::GC::AllocaAutoPtr vals_autoptr;
MMgc::GC::AllocaAutoPtr args_autoptr;
MMgc::GC::AllocaAutoPtr state_autoptr;
const InterpreterData& interpreter = JitManager::interpreter(env);
int num_values = interpreter.num_values();
AvmCore* core = env->core();
Value* values = (Value*) avmStackAlloc(core, vals_autoptr,
num_values * sizeof(Value));
void* argsout = avmStackAlloc(core, args_autoptr,
interpreter.max_argc_ * sizeof(Value));
int frame_size = env->method->getMethodSignature()->frame_size();
Value* saved_state = (Value*) avmStackAlloc(core, state_autoptr,
frame_size * sizeof(Value*));
core->stackCheck(env);
Interpreter frame(interpreter, env, argc, args, values, (uint32_t*)argsout,
saved_state);
for (BlockStartInstr* blk = interpreter.ir_->begin; true;
blk = frame.next_block_) {
// process the block, ordinary instructions, and branch.
for (InstrRange i(blk); !i.empty(); i.popFront()) {
Instr* instr = i.front();
printInstr(&frame, instr);
frame.next_block_ = 0;
do_instr(&frame, instr);
assert(checkValues(frame, instr));
}
Instr* branch = InstrGraph::blockEnd(blk);
if (kind(branch) == HR_return) {
StopInstr* return_instr = cast<StopInstr>(branch);
return frame.get(return_instr->value_in());
}
}
}
uintptr_t Interpreter::intStub(MethodEnv* env, int32_t argc, uint32_t* args) {
// sign extend int->intptr, then cast to uintptr
return (intptr_t)stub(env, argc, args).int_;
}
uintptr_t Interpreter::uintStub(MethodEnv* env, int32_t argc, uint32_t* args) {
// cast int32->uint32, then 0-extend to uintptr
return (uint32_t)stub(env, argc, args).atom_;
}
uintptr_t Interpreter::gprStub(MethodEnv* env, int32_t argc, uint32_t* args) {
return stub(env, argc, args).atom_;
}
double Interpreter::fprStub(MethodEnv* env, int32_t argc, uint32_t* args) {
return stub(env, argc, args).double_;
}
void Interpreter::do_default(Instr* instr) {
InstrKind k = kind(instr);
if (k < Stubs::stub_count && stub_table[k])
return stub_table[k](this, instr);
if (enable_verbose) {
printf("interpreter fails because on not implemented instruction:\n");
fflush(NULL);
printInstr(this, instr);
}
assert(false && "interpreter instruction not implemented");
}
Value paramToValue(const ParamIter& p) {
switch (p.model()) {
default:
assert(false && "unknown param representation");
case kModelAtom:
return AtomValue(p.asAtom());
case kModelScriptObject:
return Value(p.asObject());
case kModelString:
return Value(p.asString());
case kModelNamespace:
return Value(p.asNamespace());
case kModelInt:
return Value(p.asInt());
case kModelDouble:
return Value(p.asDouble());
}
}
/**
* Convert a default value Atom to an interpreter value.
*/
Value atomToValue(Atom value, const ParamIter& p) {
switch (p.model()) {
default:
assert(false && "unknown model type");
case kModelScriptObject:
return Value(AvmCore::isNull(value) ? (ScriptObject*)0 :
AvmCore::atomToScriptObject(value));
case kModelAtom:
return AtomValue(value);
case kModelDouble:
return Value(AvmCore::number_d(value));
case kModelInt:
switch (p.builtinType()) {
default: assert(false && "bad builtinType");
case BUILTIN_int: return Value(AvmCore::integer_i(value));
case BUILTIN_uint: return Value(AvmCore::integer_u(value));
case BUILTIN_boolean: return Value(atomGetBoolean(value));
}
case kModelString:
return Value(AvmCore::isNull(value) ? (String*) 0 :
AvmCore::atomToString(value));
case kModelNamespace:
return Value(AvmCore::isNull(value) ? (Namespace*) 0 :
AvmCore::atomToNamespace(value));
}
}
void Interpreter::do_start(StartInstr* start) {
assert(!env_->method->needArguments() && "arguments not supported yet");
assert(!isThisBoxed() && "unboxThis() not implemented");
// start signature for an as3 method is:
// [effect, env, this, arg, ...]. The data_param() accessor
// accesses [env, this, arg, ...].
// + 3 because we have effect, env, this, then real args
assert(numDefs(start) == signature()->param_count() + 3 +
(env_->method->needRestOrArguments() ? 1 : 0));
int i = 0;
resultVal(start->data_param(i++)) = env_;
ParamIter p(signature(), args_in_);
for (; !p.empty(); p.popFront(), ++i) {
// data_param[0] is env, so offset by 1
if (i > argc_ + 1) {
assert(signature()->optional_count() &&
"interpreter invoked with too many arguments");
// -2, 1 because i counts env and this; optional_index starts at 0.
int optional_index = i - 2 - signature()->requiredParamCount();
resultVal(start->data_param(i)) =
atomToValue(signature()->getDefaultValue(optional_index), p);
} else {
resultVal(start->data_param(i)) = paramToValue(p);
}
}
if (env_->method->needRest()) {
resultVal(start->data_param(i++)) = env_->createRestHelper(argc_, args_in_);
}
}
void Interpreter::do_arm(ArmInstr*) {
// nothing to do.
}
void Interpreter::do_label(LabelInstr*) {
// nothing to do.
}
/// helper: jump from block end to block start, passing arguments along.
/// Use a temp array to guarantee simultaneous copies; see test/swap.as
void Interpreter::jump(BlockEndInstr* end, BlockStartInstr* start) {
assert(numArgs(end) == numDefs(start));
Allocator scratch;
int argc = numArgs(end);
Value* temp = new (scratch) Value[argc];
ArrayRange<Use> args(argRange(end));
for (ArrayRange<Value> t(temp, argc); !t.empty(); t.popFront(), args.popFront())
t.front() = get(args.front());
ArrayRange<Def> defs(defRange(start));
for (ArrayRange<Value> t(temp, argc); !t.empty(); t.popFront(), defs.popFront())
resultVal(&defs.front()) = t.front();
next_block_ = start;
}
void Interpreter::do_goto(GotoInstr* go) {
jump(go, go->target);
}
void Interpreter::do_if(IfInstr* instr) {
jump(instr, instr->arm(getBool(instr->cond()) == kBoolTrue));
}
void Interpreter::do_switch(SwitchInstr* instr) {
jump(instr, instr->arm(getInt(instr->selector())));
}
void Interpreter::do_const(ConstantExpr* instr) {
Value& result = resultVal(instr->value());
const Type* t = type(instr->value());
switch (kind(t)) {
case kTypeName:
result = Value(nameVal(t));
break;
case kTypeOrdinal:
result = OrdValue(ordinalVal(t));
break;
case kTypeMethod:
result = Value(methodVal(t));
break;
case kTypeTraits:
result = Value(traitsVal(t));
break;
default:
switch (model(t)) {
case kModelScriptObject:
result = Value(objectVal(t));
break;
case kModelAtom:
result = AtomValue(atomVal(t, core()));
break;
case kModelNamespace:
result = Value(nsVal(t));
break;
case kModelString:
result = Value(stringVal(t));
break;
case kModelInt:
result = isBoolean(t) ? Value(boolVal(t)) :
isInt(t) ? Value(intVal(t)) :
Value(uintVal(t));
break;
case kModelDouble:
result = Value(doubleVal(t));
break;
default:
assert(false && "Unsupported const type");
}
}
}
/// TODO use resolved stuff if available
///
void Interpreter::do_callmethod(CallStmt2* instr) {
MethodEnv* callee_env = getEnv(instr->param_in());
doCall(callee_env, instr->arg_count(), instr->args(), instr->value_out());
}
void Interpreter::do_callinterface(CallStmt2* instr) {
// Signature_info is the interface method info
MethodInfo* interface_info = halfmoon::getMethod(type(instr->param_in()));
// callee_env is the CONCRETE dispatched method info
MethodEnv* callee_env = getEnv(instr->param_in());
doCallInterface(interface_info, callee_env, instr->arg_count(),
instr->args(), instr->value_out());
}
void Interpreter::coerceArgs(MethodSignaturep method_sig, int actual_argc_out,
const Use* call_args) {
ParamIter p(method_sig, args_out_);
for (int i = 0; i < actual_argc_out; ++i, p.popFront()) {
const Use& arg = call_args[i];
assert(p.model() == model(type(arg)) && "Signature model doesn't match arg model");
const Type* t = type(arg);
switch (model(t)) {
default:
assert(false && "Unsupported param type");
case kModelScriptObject:
p.asObject() = getObject(arg);
break;
case kModelNamespace:
p.asNamespace() = getNs(arg);
break;
case kModelString:
p.asString() = getString(arg);
break;
case kModelAtom:
p.asAtom() = getAtom(arg);
break;
case kModelDouble:
p.asDouble() = getDouble(arg);
break;
case kModelInt:
if (isInt(t))
p.asIntptr() = (intptr_t)getInt(arg); // sign-extend
else
p.asIntptr() = (uintptr_t)(uint32_t)getInt(arg); // zero-extend
break;
}
}
}
void Interpreter::doCallInterface(MethodInfo* interface_info, MethodEnv* callee_env,
int actual_argc_out, const Use* call_args, Def* value_out) {
coerceArgs(interface_info->getMethodSignature(), actual_argc_out, call_args);
Value& result = resultVal(value_out);
// AVM calling conventions don't count the 'this' argument.
int argc = actual_argc_out - 1;
const Type* t = type(value_out);
ModelKind k = isBottom(t) ? kModelInvalid : model(t);
uintptr_t iid = JitFriend::getIID(interface_info);
ImtThunkEnv* interfaceEnv = (ImtThunkEnv*) callee_env;
if (k != kModelDouble) {
GprImtThunkProc function = JitFriend::envImplImtGpr(callee_env);
uintptr_t retVal = function(interfaceEnv, argc, args_out_, iid);
switch (k) {
default:
printf("ModelKind is: %d\n", k);
assert(false && "Unsupported return type");
case kModelInvalid: // return type was _, i.e. no value
break;
case kModelScriptObject:
result = (ScriptObject*) retVal;
break;
case kModelString:
result = (String*) retVal;
break;
case kModelNamespace:
result = (Namespace*) retVal;
break;
case kModelAtom:
result = AtomValue(retVal);
break;
case kModelInt:
result = (int32_t) retVal;
break;
}
} else {
FprImtThunkProc function = JitFriend::envImplImtFpr(callee_env);
double retVal = function(interfaceEnv, argc, args_out_, iid);
result = retVal;
}
}
void Interpreter::doCall(MethodEnv* callee_env,
int actual_argc_out, const Use* call_args, Def* value_out) {
coerceArgs(callee_env->method->getMethodSignature(), actual_argc_out, call_args);
Value& result = resultVal(value_out);
// AVM calling conventions don't count the 'this' argument.
int argc = actual_argc_out - 1;
const Type* t = type(value_out);
ModelKind k = isBottom(t) ? kModelInvalid : model(t);
if (k != kModelDouble) {
GprMethodProc f = JitFriend::envImplGpr(callee_env);
uintptr_t val = f(callee_env, argc, args_out_);
switch (k) {
default:
printf("ModelKind is: %d\n", k);
assert(false && "Unsupported return type");
case kModelInvalid: // return type was _, i.e. no value
break;
case kModelScriptObject:
result = (ScriptObject*) val;
break;
case kModelString:
result = (String*) val;
break;
case kModelNamespace:
result = (Namespace*) val;
break;
case kModelAtom:
result = AtomValue((Atom)val);
break;
case kModelInt:
result = (int32_t) val;
break;
}
} else {
FprMethodProc f = JitFriend::envImplFpr(callee_env);
result = f(callee_env, argc, args_out_);
}
}
void Interpreter::do_getslot(CallStmt2* instr) {
const Use& object_in = instr->object_in();
const Use& slot_in = instr->param_in();
int slot = ordinalVal(type(slot_in));
const Type* object_type = type(object_in);
const Type* slot_type = lattice().getSlotType(object_type, slot);
assert(subtypeof(type(instr->value_out()), slot_type));
uint32_t offset = lattice().getSlotOffset(object_type, slot);
Value& result = resultVal(instr->value_out());
ScriptObject* object = getObject(object_in);
uintptr_t ptr = uintptr_t(object) + offset;
switch (model(slot_type)) {
default:
assert(false && "Unsupported model");
case kModelAtom:
result = AtomValue(*((Atom*) ptr));
break;
case kModelScriptObject:
result = Value(*((ScriptObject**) ptr));
break;
case kModelString:
result = Value(*((String**) ptr));
break;
case kModelNamespace:
result = Value(*((Namespace**) ptr));
break;
case kModelInt:
result = Value(*((int32_t*) ptr));
break;
case kModelDouble:
result = Value(*((double*) ptr));
break;
}
}
void Interpreter::do_setslot(CallStmt2* instr) {
const Use& name_in = instr->param_in();
const Use& object_in = instr->object_in();
const Use& value_in = instr->vararg(0);
int slot = getOrdinal(name_in);
const Type* value_type = type(value_in);
const Type* object_type = type(object_in);
assert(subtypeof(value_type, lattice().getSlotType(object_type, slot)));
assert(submodelof(value_type, lattice().getSlotType(object_type, slot)));
uint32_t offset = lattice().getSlotOffset(object_type, slot);
ScriptObject* object = getObject(object_in);
assert (object != 0);
uintptr_t ptr = uintptr_t(object) + offset;
switch (model(value_type)) {
default:
assert(false && "Unsupported rep");
case kModelAtom:
core()->atomWriteBarrier(core()->gc, object, (Atom*) ptr, getAtom(value_in));
break;
case kModelInt:
*((int32_t*) ptr) = getInt(value_in);
break;
case kModelDouble:
*((double*) ptr) = getDouble(value_in);
break;
case kModelString:
WBRC(core()->gc, object, (void*)ptr, getString(value_in));
break;
case kModelNamespace:
WBRC(core()->gc, object, (void*)ptr, getNs(value_in));
break;
case kModelScriptObject:
WBRC(core()->gc, object, (void*)ptr, getObject(value_in));
break;
}
}
void Interpreter::do_speculate_number(BinaryExpr* instr) {
Atom value = getAtom(instr->lhs_in());
assert((atomKind(value) == kDoubleType) && "Simulate Bailout");
resultVal(instr->value_out()) = core()->atomToDouble(value);
}
void Interpreter::do_speculate_int(BinaryExpr* instr) {
Atom value = getAtom(instr->lhs_in());
assert((atomKind(value) == kIntptrType) && "Simulate Bailout");
resultVal(instr->value_out()) = core()->integer_i(value);
}
/// I don't think we actually have to generate any code for newstate?
/// It seems like a pure IR placeholder
void Interpreter::do_newstate(ConstantExpr* instr) {
resultVal(instr->value()) = StateValue();
}
/// Set locals are explicit stores of the AS3 stack
/// The interpreter doesn't have to do anything until a safepoint
/// actually occurs, then we'll walk the chain of setlocals
/// and emit the explicit stores.
void Interpreter::do_setlocal(SetlocalInstr* instr) {
// I think we technically still have the same state object, just a side effect
// occured on it.
int stackIndex = instr->index;
saved_state_[stackIndex] = get(instr->value_in());
resultVal(instr->state_out()) = get(instr->state_in());
}
/// Fixme, change safepoint to also save scope stack and operand stack
/// Currently we only track locals.
void Interpreter::do_safepoint(SafepointInstr* /*instr*/) {
}
void Interpreter::do_abc_hasnext2(Hasnext2Stmt* instr) {
Atom object = getAtom(instr->object_in());
int counter = getInt(instr->counter_in());
int b = env_->hasnextproto(object, counter);
resultVal(instr->value_out()) = (b != 0);
resultVal(instr->object_out()) = AtomValue(object);
resultVal(instr->counter_out()) = counter;
}
} // end class
#include "generated/Stub_callers.hh"
#endif // VMCFG_HALFMOON