Value InterpretedVM::IndexMemberValue(uint32 typeId, byte* val, uint32 index) const { Value result; SOp compDef = prog.DefinedTypes.at(typeId); switch (compDef.Op) { case Op::OpTypeVector: { auto vec = (STypeVector*)compDef.Memory; uint32 offset = GetTypeByteSize(vec->ComponenttypeId) * index; result.TypeId = vec->ComponenttypeId; result.Memory = val + offset; break; } case Op::OpTypeStruct: { auto s = (STypeStruct*)compDef.Memory; result.TypeId = s->MembertypeIds[index]; result.Memory = val; for (int i = 0; i < index; i++) { result.Memory += GetTypeByteSize(s->MembertypeIds[i]); } break; } case Op::OpTypePointer: { auto p = (STypePointer*)compDef.Memory; result = IndexMemberValue(p->TypeId, (byte*)*(void**)val, index); break; } default: result.Memory = nullptr; result.TypeId = 0; std::cout << "Not a composite type def: " << writeOp(compDef); } return result; }
Value InterpretedVM::VmInit(uint32 typeId, void* value) { Value val = { typeId, VmAlloc(typeId) }; if (value) { memcpy(val.Memory, value, GetTypeByteSize(val.TypeId)); } else { memset(val.Memory, 0, GetTypeByteSize(val.TypeId)); } return val; }
uint32 InterpretedVM::GetTypeByteSize(uint32 typeId) const { if (TypeByteSizes.find(typeId) != TypeByteSizes.end()) { return TypeByteSizes.at(typeId); } auto definedType = prog.DefinedTypes.at(typeId); uint32 size = 0; switch (definedType.Op) { case Op::OpTypeArray: { auto arr = (STypeArray*)definedType.Memory; size = GetTypeByteSize(size) * *(uint32*)env.Values[arr->LengthId].Memory; break; } case Op::OpTypeInt: { auto i = (STypeInt*)definedType.Memory; assert(i->Width % 8 == 0); size = i->Width / 8; break; } case Op::OpTypeFloat: { auto f = (STypeFloat*)definedType.Memory; assert(f->Width % 8 == 0); size = f->Width / 8; break; } case Op::OpTypeBool: { size = sizeof(bool); break; } case Op::OpTypePointer: { size = sizeof(void*); break; } case Op::OpTypeStruct: { auto s = (STypeStruct*)definedType.Memory; for (int i = 0; i < s->MembertypeIdsCount; i++) { uint32 id = s->MembertypeIds[i]; size += GetTypeByteSize(id); } break; } case Op::OpTypeVector: { auto v = (STypeVector*)definedType.Memory; size = GetTypeByteSize(v->ComponenttypeId) * v->Componentcount; break; } default: std::cout << "Not a type definition: " << writeOp(definedType); } //TODO: Precalc type sizes //TypeByteSizes[typeId] = size; return size; }
WAsmJs::TypedConstSourcesInfo TypedRegisterAllocator::GetConstSourceInfos() const { WAsmJs::TypedConstSourcesInfo infos; // The const table doesn't contain the first reg slots (aka return register) uint32 offset = ConvertOffset<Js::Var, byte>(Js::AsmJsFunctionMemory::RequiredVarConstants - Js::FunctionBody::FirstRegSlot); for (int i = 0; i < WAsmJs::LIMIT; ++i) { Types type = (Types)i; if (!IsTypeExcluded(type)) { infos.srcByteOffsets[i] = offset; uint32 typeSize = GetTypeByteSize(type); uint32 constCount = GetRegisterSpace(type)->GetConstCount(); uint32 typeBytesUsage = ConvertOffset<byte>(constCount, typeSize); offset = Math::AlignOverflowCheck(offset, typeSize); offset = UInt32Math::Add(offset, typeBytesUsage); } else { infos.srcByteOffsets[i] = (uint32)Js::Constants::InvalidOffset; } } infos.bytesUsed = offset; return infos; }
bool InterpretedVM::InitializeConstants() { for (auto& constant : prog.Constants) { auto op = constant.second; switch (op.Op) { case Op::OpConstant: { auto constant = (SConstant*)op.Memory; Value val = { constant->ResultTypeId, (byte*)constant->Values }; env.Values[constant->ResultId] = val; break; } case Op::OpConstantComposite: { auto constant = (SConstantComposite*)op.Memory; Value val = { constant->ResultTypeId, VmAlloc(constant->ResultTypeId) }; env.Values[constant->ResultId] = val; byte* memPtr = val.Memory; for (int i = 0; i < constant->ConstituentsIdsCount; i++) { auto memVal = env.Values[constant->ConstituentsIds[i]]; uint32 memSize = GetTypeByteSize(memVal.TypeId); memcpy(memPtr, memVal.Memory, memSize); memPtr += memSize; } assert( memPtr - val.Memory == GetTypeByteSize(constant->ResultTypeId)); break; } case Op::OpConstantFalse: { auto constant = (SConstantFalse*)op.Memory; Value val = { constant->ResultTypeId, VmAlloc(constant->ResultTypeId) }; *(bool*)val.Memory = false; break; } case Op::OpConstantTrue: { auto constant = (SConstantTrue*)op.Memory; Value val = { constant->ResultTypeId, VmAlloc(constant->ResultTypeId) }; *(bool*)val.Memory = true; break; } default: std::cout << "Operation does not define a constant: " << writeOp(op); return false; } } return true; }
bool InterpretedVM::SetVariable(uint32 id, void* value) { SVariable var; if (prog.CurrentFunction->Variables.find(id) != prog.CurrentFunction->Variables.end()) { var = prog.CurrentFunction->Variables.at(id); } else { var = prog.Variables.at(id); } if (env.Values.find(var.ResultId) == env.Values.end()) { Value val = { var.ResultTypeId, VmAlloc(var.ResultTypeId) }; if (value) { memcpy(val.Memory, value, GetTypeByteSize(val.TypeId)); } else { memset(val.Memory, 0, GetTypeByteSize(val.TypeId)); } env.Values[var.ResultId] = val; } else { Value val = env.Values[var.ResultId]; memcpy(val.Memory, value, GetTypeByteSize(val.TypeId)); } return true; }
byte* InterpretedVM::VmAlloc(uint32 typeId) { uint32 compositeSize = GetTypeByteSize(typeId); byte* mem = (byte*)malloc(compositeSize); VmMemory.push_back(std::unique_ptr<byte>(mem)); return (byte*)mem; }
uint32 InterpretedVM::Execute(Function* func) { prog.CurrentFunction = func; int pc = 0; for (;;) { auto op = func->Ops[pc]; switch (op.Op) { case Op::OpBranch: { auto branch = (SBranch*)op.Memory; pc = func->Labels.at(branch->TargetLabelId); break; } case Op::OpBranchConditional: { auto branch = (SBranchConditional*)op.Memory; uint32 labelID; Value val = Dereference(env.Values[branch->ConditionId]); if (*(bool*)val.Memory) { labelID = branch->TrueLabelId; } else { labelID = branch->FalseLabelId; } pc = func->Labels.at(labelID); break; } case Op::OpFunctionCall: { auto call = (SFunctionCall*)op.Memory; Function toCall = prog.FunctionDefinitions.at(call->FunctionId); for (int i = 0; i < call->ArgumentIdsCount; i++) { env.Values[toCall.Parameters[i].ResultId] = Dereference(env.Values.at(call->ArgumentIds[i])); } uint32 resultId = Execute(&toCall); if (resultId == -1) { return -1; } prog.CurrentFunction = func; env.Values[call->ResultId] = env.Values[resultId]; break; } case Op::OpExtInst: { auto extInst = (SExtInst*)op.Memory; Value* ops = new Value[extInst->OperandIdsCount]; for (int i = 0; i < extInst->OperandIdsCount; i++) { ops[i] = Dereference(env.Values.at(extInst->OperandIds[i])); } ExtInstFunc* func = env.Extensions[extInst->SetId][extInst->Instruction]; env.Values[extInst->ResultId] = func(this, extInst->ResultTypeId, extInst->OperandIdsCount, ops); break; } case Op::OpConvertSToF: { auto convert = (SConvertSToF*)op.Memory; Value op1 = Dereference(env.Values[convert->SignedValueId]); env.Values[convert->ResultId] = DoOp(convert->ResultTypeId, Convert<int32, float>, op1); break; } case Op::OpFAdd: { auto add = (SFAdd*)op.Memory; Value op1 = Dereference(env.Values[add->Operand1Id]); Value op2 = Dereference(env.Values[add->Operand2Id]); env.Values[add->ResultId] = DoOp(add->ResultTypeId, Add<float>, op1, op2); break; } case Op::OpIAdd: { auto add = (SIAdd*)op.Memory; Value op1 = Dereference(env.Values[add->Operand1Id]); Value op2 = Dereference(env.Values[add->Operand2Id]); env.Values[add->ResultId] = DoOp(add->ResultTypeId, Add<int>, op1, op2); break; } case Op::OpFSub: { auto sub = (SFSub*)op.Memory; Value op1 = Dereference(env.Values[sub->Operand1Id]); Value op2 = Dereference(env.Values[sub->Operand2Id]); env.Values[sub->ResultId] = DoOp(sub->ResultTypeId, Sub<float>, op1, op2); break; } case Op::OpISub: { auto sub = (SISub*)op.Memory; Value op1 = Dereference(env.Values[sub->Operand1Id]); Value op2 = Dereference(env.Values[sub->Operand2Id]); env.Values[sub->ResultId] = DoOp(sub->ResultTypeId, Sub<int>, op1, op2); break; } case Op::OpFDiv: { auto div = (SFDiv*)op.Memory; Value op1 = Dereference(env.Values[div->Operand1Id]); Value op2 = Dereference(env.Values[div->Operand2Id]); env.Values[div->ResultId] = DoOp(div->ResultTypeId, Div<float>, op1, op2); break; } case Op::OpFMul: { auto mul = (SFMul*)op.Memory; Value op1 = Dereference(env.Values[mul->Operand1Id]); Value op2 = Dereference(env.Values[mul->Operand2Id]); env.Values[mul->ResultId] = DoOp(mul->ResultTypeId, Mul<float>, op1, op2); break; } case Op::OpIMul: { auto mul = (SFMul*)op.Memory; Value op1 = Dereference(env.Values[mul->Operand1Id]); Value op2 = Dereference(env.Values[mul->Operand2Id]); env.Values[mul->ResultId] = DoOp(mul->ResultTypeId, Mul<int>, op1, op2); break; } case Op::OpVectorTimesScalar: { auto vts = (SVectorTimesScalar*)op.Memory; Value scalar = Dereference(env.Values[vts->ScalarId]); Value vector = Dereference(env.Values[vts->VectorId]); env.Values[vts->ResultId] = DoOp(vts->ResultTypeId, [scalar](Value comp) {return Mul<float>(scalar, comp);}, vector); break; } case Op::OpSLessThan: { auto lessThan = (SSLessThan*)op.Memory; Value op1 = Dereference(env.Values[lessThan->Operand1Id]); Value op2 = Dereference(env.Values[lessThan->Operand2Id]); env.Values[lessThan->ResultId] = DoOp(lessThan->ResultTypeId, [](Value a, Value b) { return Cmp<int32>(a, b) == -1; }, op1, op2); break; } case Op::OpSGreaterThan: { auto greaterThan = (SSLessThan*)op.Memory; Value op1 = Dereference(env.Values[greaterThan->Operand1Id]); Value op2 = Dereference(env.Values[greaterThan->Operand2Id]); env.Values[greaterThan->ResultId] = DoOp(greaterThan->ResultTypeId, [](Value a, Value b) { return Cmp<int32>(a, b) == 1; }, op1, op2); break; } case Op::OpLoad: { auto load = (SLoad*)op.Memory; auto valueToLoad = env.Values.at(load->PointerId); env.Values[load->ResultId] = valueToLoad; break; } case Op::OpStore: { auto store = (SStore*)op.Memory; auto val = env.Values[store->ObjectId]; auto var = GetType(val.TypeId); if (var.Op == Op::OpTypePointer) { SetVariable(store->PointerId, val.Memory); } else { SetVariable(store->PointerId, &val.Memory); } break; } case Op::OpTextureSample: { auto sample = (STextureSample*)op.Memory; auto sampler = Dereference(env.Values.at(sample->SamplerId)); auto coord = Dereference(env.Values.at(sample->CoordinateId)); Value bias = { 0, 0 }; if (sample->BiasId != 0) { bias = Dereference(env.Values.at(sample->BiasId)); } env.Values[sample->ResultId] = TextureSample(sampler, coord, bias, sample->ResultTypeId); break; } case Op::OpLabel: case Op::OpSelectionMerge: case Op::OpLoopMerge: break; case Op::OpAccessChain: { auto access = (SAccessChain*)op.Memory; auto val = Dereference(env.Values.at(access->BaseId)); uint32* indices = new uint32[access->IndexesIdsCount]; for (int i = 0; i < access->IndexesIdsCount; i++) { indices[i] = *(uint32*)Dereference(env.Values[access->IndexesIds[i]]).Memory; } byte* mem = GetPointerInComposite(val.TypeId, val.Memory, access->IndexesIdsCount, indices); delete indices; Value res = VmInit(access->ResultTypeId, &mem); env.Values[access->ResultId] = res; break; } case Op::OpVectorShuffle: { auto vecShuffle = (SVectorShuffle*)op.Memory; auto vec1 = Dereference(env.Values.at(vecShuffle->Vector1Id)); auto vec2 = Dereference(env.Values.at(vecShuffle->Vector2Id)); auto result = VmInit(vecShuffle->ResultTypeId, nullptr); int v1ElCount = ElementCount(vec1.TypeId); for (int i = 0; i < vecShuffle->ComponentsCount; i++) { int index = vecShuffle->Components[i]; Value toCopy; if (index < v1ElCount) { toCopy = vec1; } else { index -= v1ElCount; toCopy = vec2; } Value elToCopy = IndexMemberValue(toCopy, index); memcpy(IndexMemberValue(result, i).Memory, elToCopy.Memory, GetTypeByteSize(elToCopy.TypeId)); } env.Values[vecShuffle->ResultId] = result; break; } //TODO: FIX INDICES (NOT HIERARCHY!) case Op::OpCompositeExtract: { auto extract = (SCompositeExtract*)op.Memory; auto composite = env.Values[extract->CompositeId]; byte* mem = GetPointerInComposite(composite.TypeId, composite.Memory, extract->IndexesCount, extract->Indexes); Value val = { extract->ResultTypeId, VmAlloc(extract->ResultTypeId) }; memcpy(val.Memory, mem, GetTypeByteSize(val.TypeId)); env.Values[extract->ResultId] = val; break; } case Op::OpCompositeInsert: { auto insert = (SCompositeInsert*)op.Memory; auto composite = Dereference(env.Values[insert->CompositeId]); Value val = Dereference(env.Values.at(insert->ObjectId)); byte* mem = GetPointerInComposite(composite.TypeId, composite.Memory, insert->IndexesCount, insert->Indexes); memcpy(mem, val.Memory, GetTypeByteSize(val.TypeId)); env.Values[insert->ResultId] = VmInit(composite.TypeId, composite.Memory); break; } case Op::OpCompositeConstruct: { auto construct = (SCompositeConstruct*)op.Memory; Value val = { construct->ResultTypeId, VmAlloc(construct->ResultTypeId) }; env.Values[construct->ResultId] = val; byte* memPtr = val.Memory; for (int i = 0; i < construct->ConstituentsIdsCount; i++) { auto memVal = env.Values[construct->ConstituentsIds[i]]; uint32 memSize = GetTypeByteSize(memVal.TypeId); memcpy(memPtr, memVal.Memory, memSize); memPtr += memSize; } assert(memPtr - val.Memory == GetTypeByteSize(construct->ResultTypeId)); break; } case Op::OpVariable: { auto var = (SVariable*)op.Memory; Value val = { var->ResultTypeId, VmAlloc(var->ResultTypeId) }; if (var->InitializerId) { memcpy(val.Memory, env.Values[var->InitializerId].Memory, GetTypeByteSize(val.TypeId)); } else { memset(val.Memory, 0, GetTypeByteSize(val.TypeId)); } env.Values[var->ResultId] = val; break; } case Op::OpReturnValue: { auto ret = (SReturnValue*)op.Memory; return ret->ValueId; } case Op::OpReturn: return 0; default: std::cout << "Unimplemented operation: " << writeOp(op); return -1; } pc++; } return 0; }
void TypedRegisterAllocator::CommitToFunctionInfo(Js::AsmJsFunctionInfo* funcInfo, Js::FunctionBody* body) const { uint32 offset = Js::AsmJsFunctionMemory::RequiredVarConstants * sizeof(Js::Var); WAsmJs::TypedConstSourcesInfo constSourcesInfo = GetConstSourceInfos(); #if DBG_DUMP if (PHASE_TRACE(Js::AsmjsInterpreterStackPhase, body)) { Output::Print(_u("ASMFunctionInfo Stack Data for %s\n"), body->GetDisplayName()); Output::Print(_u("==========================\n")); Output::Print(_u("RequiredVarConstants:%d\n"), Js::AsmJsFunctionMemory::RequiredVarConstants); } #endif for (int i = 0; i < WAsmJs::LIMIT; ++i) { Types type = (Types)i; TypedSlotInfo& slotInfo = *funcInfo->GetTypedSlotInfo(type); // Check if we don't want to commit this type if (!IsTypeExcluded(type)) { RegisterSpace* registerSpace = GetRegisterSpace(type); slotInfo.constCount = registerSpace->GetConstCount(); slotInfo.varCount = registerSpace->GetVarCount(); slotInfo.tmpCount = registerSpace->GetTmpCount(); slotInfo.constSrcByteOffset = constSourcesInfo.srcByteOffsets[i]; offset = Math::AlignOverflowCheck(offset, GetTypeByteSize(type)); slotInfo.byteOffset = offset; // Update offset for next type uint32 totalTypeCount = 0; totalTypeCount = UInt32Math::Add(totalTypeCount, slotInfo.constCount); totalTypeCount = UInt32Math::Add(totalTypeCount, slotInfo.varCount); totalTypeCount = UInt32Math::Add(totalTypeCount, slotInfo.tmpCount); offset = UInt32Math::Add(offset, UInt32Math::Mul(totalTypeCount, GetTypeByteSize(type))); #if DBG_DUMP if (PHASE_TRACE(Js::AsmjsInterpreterStackPhase, body)) { char16 buf[16]; RegisterSpace::GetTypeDebugName(type, buf, 16); Output::Print(_u("%s Offset:%d ConstCount:%d VarCount:%d TmpCount:%d = %d * %d = 0x%x bytes\n"), buf, slotInfo.byteOffset, slotInfo.constCount, slotInfo.varCount, slotInfo.tmpCount, totalTypeCount, GetTypeByteSize(type), totalTypeCount * GetTypeByteSize(type)); } #endif } } // These bytes offset already calculated the alignment, used them to determine how many Js::Var we need to do the allocation uint32 stackByteSize = offset; uint32 bytesUsedForConst = constSourcesInfo.bytesUsed; uint32 jsVarUsedForConstsTable = ConvertToJsVarOffset<byte>(bytesUsedForConst); uint32 totalVarsNeeded = ConvertToJsVarOffset<byte>(stackByteSize); uint32 jsVarNeededForVars = totalVarsNeeded - jsVarUsedForConstsTable; if (totalVarsNeeded < jsVarUsedForConstsTable) { // If for some reason we allocated more space in the const table than what we need, just don't allocate anymore vars jsVarNeededForVars = 0; } Assert((jsVarUsedForConstsTable + jsVarNeededForVars) >= totalVarsNeeded); body->CheckAndSetVarCount(jsVarNeededForVars); #if DBG_DUMP if (PHASE_TRACE(Js::AsmjsInterpreterStackPhase, body)) { Output::Print(_u("Total memory required: (%u consts + %u vars) * sizeof(Js::Var) >= %u * sizeof(Js::Var) = 0x%x bytes\n"), jsVarUsedForConstsTable, jsVarNeededForVars, totalVarsNeeded, stackByteSize); } #endif }