blob: 6c47c8b4a5cd8b71427d4de45179fdeaf9d9a79f [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// SIMD_JS
// GlobOpt bits related to SIMD.js
#include "Backend.h"
#ifdef ENABLE_SIMDJS
/*
Handles all Simd128 type-spec of an instr, if possible.
*/
bool
GlobOpt::TypeSpecializeSimd128(
IR::Instr *instr,
Value **pSrc1Val,
Value **pSrc2Val,
Value **pDstVal
)
{
if (func->GetJITFunctionBody()->IsAsmJsMode() || SIMD128_TYPE_SPEC_FLAG == false)
{
// no type-spec for ASMJS code or flag is off.
return false;
}
switch (instr->m_opcode)
{
case Js::OpCode::ArgOut_A_InlineBuiltIn:
if (instr->GetSrc1()->IsRegOpnd())
{
StackSym *sym = instr->GetSrc1()->AsRegOpnd()->m_sym;
if (this->currentBlock->globOptData.IsSimd128TypeSpecialized(sym))
{
ValueType valueType = (*pSrc1Val)->GetValueInfo()->Type();
Assert(valueType.IsSimd128());
ToTypeSpecUse(instr, instr->GetSrc1(), this->currentBlock, *pSrc1Val, nullptr, GetIRTypeFromValueType(valueType), GetBailOutKindFromValueType(valueType));
return true;
}
}
return false;
case Js::OpCode::Ld_A:
if (instr->GetSrc1()->IsRegOpnd())
{
StackSym *sym = instr->GetSrc1()->AsRegOpnd()->m_sym;
IRType type = TyIllegal;
if (this->currentBlock->globOptData.IsSimd128F4TypeSpecialized(sym))
{
type = TySimd128F4;
}
else if (this->currentBlock->globOptData.IsSimd128I4TypeSpecialized(sym))
{
type = TySimd128I4;
}
else
{
return false;
}
ToTypeSpecUse(instr, instr->GetSrc1(), this->currentBlock, *pSrc1Val, nullptr, type, IR::BailOutSimd128F4Only /*not used for Ld_A*/);
TypeSpecializeSimd128Dst(type, instr, *pSrc1Val, *pSrc1Val, pDstVal);
return true;
}
return false;
case Js::OpCode::ExtendArg_A:
if (Simd128DoTypeSpec(instr, *pSrc1Val, *pSrc2Val, *pDstVal))
{
Assert(instr->m_opcode == Js::OpCode::ExtendArg_A);
Assert(instr->GetDst()->GetType() == TyVar);
ValueType valueType = instr->GetDst()->GetValueType();
// Type-spec src1 only based on dst type. Dst type is set by the inliner based on func signature.
ToTypeSpecUse(instr, instr->GetSrc1(), this->currentBlock, *pSrc1Val, nullptr, GetIRTypeFromValueType(valueType), GetBailOutKindFromValueType(valueType), true /*lossy*/);
ToVarRegOpnd(instr->GetDst()->AsRegOpnd(), this->currentBlock);
return true;
}
return false;
}
if (!Js::IsSimd128Opcode(instr->m_opcode))
{
return false;
}
// Simd instr
if (Simd128DoTypeSpec(instr, *pSrc1Val, *pSrc2Val, *pDstVal))
{
ThreadContext::SimdFuncSignature simdFuncSignature;
instr->m_func->GetScriptContext()->GetThreadContext()->GetSimdFuncSignatureFromOpcode(instr->m_opcode, simdFuncSignature);
// type-spec logic
// special handling for load/store
// OptArraySrc will type-spec the array and the index. We type-spec the value here.
if (Js::IsSimd128Load(instr->m_opcode))
{
TypeSpecializeSimd128Dst(GetIRTypeFromValueType(simdFuncSignature.returnType), instr, nullptr, *pSrc1Val, pDstVal);
Simd128SetIndirOpndType(instr->GetSrc1()->AsIndirOpnd(), instr->m_opcode);
return true;
}
if (Js::IsSimd128Store(instr->m_opcode))
{
ToTypeSpecUse(instr, instr->GetSrc1(), this->currentBlock, *pSrc1Val, nullptr, GetIRTypeFromValueType(simdFuncSignature.args[2]), GetBailOutKindFromValueType(simdFuncSignature.args[2]));
Simd128SetIndirOpndType(instr->GetDst()->AsIndirOpnd(), instr->m_opcode);
return true;
}
// For op with ExtendArg. All sources are already type-specialized, just type-specialize dst
if (simdFuncSignature.argCount <= 2)
{
Assert(instr->GetSrc1());
ToTypeSpecUse(instr, instr->GetSrc1(), this->currentBlock, *pSrc1Val, nullptr, GetIRTypeFromValueType(simdFuncSignature.args[0]), GetBailOutKindFromValueType(simdFuncSignature.args[0]));
if (instr->GetSrc2())
{
ToTypeSpecUse(instr, instr->GetSrc2(), this->currentBlock, *pSrc2Val, nullptr, GetIRTypeFromValueType(simdFuncSignature.args[1]), GetBailOutKindFromValueType(simdFuncSignature.args[1]));
}
}
if (instr->GetDst())
{
TypeSpecializeSimd128Dst(GetIRTypeFromValueType(simdFuncSignature.returnType), instr, nullptr, *pSrc1Val, pDstVal);
}
return true;
}
else
{
// We didn't type-spec
if (!IsLoopPrePass())
{
// Emit bailout if not loop prepass.
// The inliner inserts bytecodeUses of original args after the instruction. Bailout is safe.
IR::Instr * bailoutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNoSimdTypeSpec, IR::BailOutNoSimdTypeSpec, instr, instr->m_func);
bailoutInstr->SetByteCodeOffset(instr);
instr->InsertAfter(bailoutInstr);
instr->m_opcode = Js::OpCode::Nop;
if (instr->GetSrc1())
{
instr->FreeSrc1();
if (instr->GetSrc2())
{
instr->FreeSrc2();
}
}
if (instr->GetDst())
{
instr->FreeDst();
}
if (this->byteCodeUses)
{
// All inlined SIMD ops have jitOptimizedReg srcs
Assert(this->byteCodeUses->IsEmpty());
JitAdelete(this->alloc, this->byteCodeUses);
this->byteCodeUses = nullptr;
}
RemoveCodeAfterNoFallthroughInstr(bailoutInstr);
return true;
}
}
return false;
}
bool
GlobOpt::Simd128DoTypeSpec(IR::Instr *instr, const Value *src1Val, const Value *src2Val, const Value *dstVal)
{
bool doTypeSpec = true;
// TODO: Some operations require additional opnd constraints (e.g. shuffle/swizzle).
if (Js::IsSimd128Opcode(instr->m_opcode))
{
ThreadContext::SimdFuncSignature simdFuncSignature;
instr->m_func->GetScriptContext()->GetThreadContext()->GetSimdFuncSignatureFromOpcode(instr->m_opcode, simdFuncSignature);
if (!simdFuncSignature.valid)
{
// not implemented yet.
return false;
}
// special handling for Load/Store
if (Js::IsSimd128Load(instr->m_opcode) || Js::IsSimd128Store(instr->m_opcode))
{
return Simd128DoTypeSpecLoadStore(instr, src1Val, src2Val, dstVal, &simdFuncSignature);
}
const uint argCount = simdFuncSignature.argCount;
switch (argCount)
{
case 2:
Assert(src2Val);
doTypeSpec = doTypeSpec && Simd128CanTypeSpecOpnd(src2Val->GetValueInfo()->Type(), simdFuncSignature.args[1]) && Simd128ValidateIfLaneIndex(instr, instr->GetSrc2(), 1);
// fall-through
case 1:
Assert(src1Val);
doTypeSpec = doTypeSpec && Simd128CanTypeSpecOpnd(src1Val->GetValueInfo()->Type(), simdFuncSignature.args[0]) && Simd128ValidateIfLaneIndex(instr, instr->GetSrc1(), 0);
break;
default:
{
// extended args
Assert(argCount > 2);
// Check if all args have been type specialized.
int arg = argCount - 1;
IR::Instr * eaInstr = GetExtendedArg(instr);
while (arg>=0)
{
Assert(eaInstr);
Assert(eaInstr->m_opcode == Js::OpCode::ExtendArg_A);
ValueType expectedType = simdFuncSignature.args[arg];
IR::Opnd * opnd = eaInstr->GetSrc1();
StackSym * sym = opnd->GetStackSym();
// In Forward Prepass: Check liveness through liveness bits, not IR type, since in prepass no actual type-spec happens.
// In the Forward Pass: Check IRType since Sym can be null, because of const prop.
if (expectedType.IsSimd128Float32x4())
{
if (sym && !this->currentBlock->globOptData.IsSimd128F4TypeSpecialized(sym) ||
!sym && opnd->GetType() != TySimd128F4)
{
return false;
}
}
else if (expectedType.IsSimd128Int32x4())
{
if (sym && !this->currentBlock->globOptData.IsSimd128I4TypeSpecialized(sym) ||
!sym && opnd->GetType() != TySimd128I4)
{
return false;
}
}
else if (expectedType.IsFloat())
{
if (sym && !this->currentBlock->globOptData.IsFloat64TypeSpecialized(sym) ||
!sym&& opnd->GetType() != TyFloat64)
{
return false;
}
}
else if (expectedType.IsInt())
{
if ((sym && !this->currentBlock->globOptData.IsInt32TypeSpecialized(sym) && !currentBlock->globOptData.liveLossyInt32Syms->Test(sym->m_id)) ||
!sym && opnd->GetType() != TyInt32)
{
return false;
}
// Extra check if arg is a lane index
if (!Simd128ValidateIfLaneIndex(instr, opnd, arg))
{
return false;
}
}
else
{
Assert(UNREACHED);
}
eaInstr = GetExtendedArg(eaInstr);
arg--;
}
// all args are type-spec'd
doTypeSpec = true;
}
}
}
else
{
Assert(instr->m_opcode == Js::OpCode::ExtendArg_A);
// For ExtendArg, the expected type is encoded in the dst(link) operand.
doTypeSpec = doTypeSpec && Simd128CanTypeSpecOpnd(src1Val->GetValueInfo()->Type(), instr->GetDst()->GetValueType());
}
return doTypeSpec;
}
bool
GlobOpt::Simd128DoTypeSpecLoadStore(IR::Instr *instr, const Value *src1Val, const Value *src2Val, const Value *dstVal, const ThreadContext::SimdFuncSignature *simdFuncSignature)
{
IR::Opnd *baseOpnd = nullptr, *indexOpnd = nullptr, *valueOpnd = nullptr;
IR::Opnd *src, *dst;
bool doTypeSpec = true;
// value = Ld [arr + index]
// [arr + index] = St value
src = instr->GetSrc1();
dst = instr->GetDst();
Assert(dst && src && !instr->GetSrc2());
if (Js::IsSimd128Load(instr->m_opcode))
{
Assert(src->IsIndirOpnd());
baseOpnd = instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd();
indexOpnd = instr->GetSrc1()->AsIndirOpnd()->GetIndexOpnd();
valueOpnd = instr->GetDst();
}
else if (Js::IsSimd128Store(instr->m_opcode))
{
Assert(dst->IsIndirOpnd());
baseOpnd = instr->GetDst()->AsIndirOpnd()->GetBaseOpnd();
indexOpnd = instr->GetDst()->AsIndirOpnd()->GetIndexOpnd();
valueOpnd = instr->GetSrc1();
// St(arr, index, value). Make sure value can be Simd128 type-spec'd
doTypeSpec = doTypeSpec && Simd128CanTypeSpecOpnd(this->currentBlock->globOptData.FindValue(valueOpnd->AsRegOpnd()->m_sym)->GetValueInfo()->Type(), simdFuncSignature->args[2]);
}
else
{
Assert(UNREACHED);
}
// array and index operands should have been type-specialized in OptArraySrc: ValueTypes should be definite at this point. If not, don't type-spec.
// We can be in a loop prepass, where opnd ValueInfo is not set yet. Get the ValueInfo from the Value Table instead.
ValueType baseOpndType = this->currentBlock->globOptData.FindValue(baseOpnd->AsRegOpnd()->m_sym)->GetValueInfo()->Type();
if (IsLoopPrePass())
{
doTypeSpec = doTypeSpec && (baseOpndType.IsObject() && baseOpndType.IsTypedArray());
// indexOpnd might be missing if loading from [0]
if (indexOpnd != nullptr)
{
ValueType indexOpndType = this->currentBlock->globOptData.FindValue(indexOpnd->AsRegOpnd()->m_sym)->GetValueInfo()->Type();
doTypeSpec = doTypeSpec && indexOpndType.IsLikelyInt();
}
}
else
{
doTypeSpec = doTypeSpec && (baseOpndType.IsObject() && baseOpndType.IsTypedArray());
if (indexOpnd != nullptr)
{
ValueType indexOpndType = this->currentBlock->globOptData.FindValue(indexOpnd->AsRegOpnd()->m_sym)->GetValueInfo()->Type();
doTypeSpec = doTypeSpec && indexOpndType.IsInt();
}
}
return doTypeSpec;
}
// We can type spec an opnd if:
// Both profiled/propagated and expected types are not Simd128. e.g. expected type is f64/f32/i32 where there is a conversion logic from the incoming type.
// Opnd type is (Likely) SIMD128 and matches expected type.
// Opnd type is Object. e.g. possibly result of merging different SIMD types. We specialize because we don't know which pass is dynamically taken.
bool GlobOpt::Simd128CanTypeSpecOpnd(const ValueType opndType, ValueType expectedType)
{
if (!opndType.IsSimd128() && !expectedType.IsSimd128())
{
// Non-Simd types can be coerced or we bailout by a FromVar.
return true;
}
// Simd type
if (opndType.HasBeenNull() || opndType.HasBeenUndefined())
{
return false;
}
if (
(opndType.IsLikelyObject() && opndType.ToDefiniteObject() == expectedType) ||
(opndType.IsLikelyObject() && opndType.GetObjectType() == ObjectType::Object)
)
{
return true;
}
return false;
}
/*
Given an instr, opnd and the opnd position. Return true if opnd is a lane index and valid, or not a lane index all-together..
*/
bool GlobOpt::Simd128ValidateIfLaneIndex(const IR::Instr * instr, IR::Opnd * opnd, uint argPos)
{
Assert(instr);
Assert(opnd);
uint laneIndex;
uint argPosLo, argPosHi;
uint laneIndexLo, laneIndexHi;
// operation takes a lane index ?
switch (instr->m_opcode)
{
case Js::OpCode::Simd128_Swizzle_F4:
case Js::OpCode::Simd128_Swizzle_I4:
argPosLo = 1; argPosHi = 4;
laneIndexLo = 0; laneIndexHi = 3;
break;
case Js::OpCode::Simd128_Shuffle_F4:
case Js::OpCode::Simd128_Shuffle_I4:
argPosLo = 2; argPosHi = 5;
laneIndexLo = 0; laneIndexHi = 7;
break;
case Js::OpCode::Simd128_ReplaceLane_F4:
case Js::OpCode::Simd128_ReplaceLane_I4:
case Js::OpCode::Simd128_ExtractLane_F4:
case Js::OpCode::Simd128_ExtractLane_I4:
argPosLo = argPosHi = 1;
laneIndexLo = 0; laneIndexHi = 3;
break;
default:
return true; // not a lane index
}
// arg in lane index pos of operation ?
if (argPos < argPosLo || argPos > argPosHi)
{
return true; // not a lane index
}
// It is a lane index ...
// Arg is Int constant (literal or const prop'd) ?
if (!opnd->IsIntConstOpnd())
{
return false;
}
laneIndex = (uint) opnd->AsIntConstOpnd()->GetValue();
// In range ?
if (laneIndex < laneIndexLo|| laneIndex > laneIndexHi)
{
return false;
}
return true;
}
IR::Instr * GlobOpt::GetExtendedArg(IR::Instr *instr)
{
IR::Opnd *src1, *src2;
src1 = instr->GetSrc1();
src2 = instr->GetSrc2();
if (instr->m_opcode == Js::OpCode::ExtendArg_A)
{
if (src2)
{
// mid chain
Assert(src2->GetStackSym()->IsSingleDef());
return src2->GetStackSym()->GetInstrDef();
}
else
{
// end of chain
return nullptr;
}
}
else
{
// start of chain
Assert(Js::IsSimd128Opcode(instr->m_opcode));
Assert(src1);
Assert(src1->GetStackSym()->IsSingleDef());
return src1->GetStackSym()->GetInstrDef();
}
}
#endif
IRType GlobOpt::GetIRTypeFromValueType(const ValueType &valueType)
{
if (valueType.IsFloat())
{
return TyFloat64;
}
else if (valueType.IsInt())
{
return TyInt32;
}
else if (valueType.IsSimd128Float32x4())
{
return TySimd128F4;
}
else
{
Assert(valueType.IsSimd128Int32x4());
return TySimd128I4;
}
}
ValueType GlobOpt::GetValueTypeFromIRType(const IRType &type)
{
switch (type)
{
case TyInt32:
return ValueType::GetInt(false);
case TyFloat64:
return ValueType::Float;
case TySimd128F4:
return ValueType::GetSimd128(ObjectType::Simd128Float32x4);
case TySimd128I4:
return ValueType::GetSimd128(ObjectType::Simd128Int32x4);
default:
Assert(UNREACHED);
}
return ValueType::UninitializedObject;
}
IR::BailOutKind GlobOpt::GetBailOutKindFromValueType(const ValueType &valueType)
{
if (valueType.IsFloat())
{
// if required valueType is Float, then allow coercion from any primitive except String.
return IR::BailOutPrimitiveButString;
}
else if (valueType.IsInt())
{
return IR::BailOutIntOnly;
}
else if (valueType.IsSimd128Float32x4())
{
return IR::BailOutSimd128F4Only;
}
else
{
Assert(valueType.IsSimd128Int32x4());
return IR::BailOutSimd128I4Only;
}
}
#ifdef ENABLE_SIMDJS
void
GlobOpt::UpdateBoundCheckHoistInfoForSimd(ArrayUpperBoundCheckHoistInfo &upperHoistInfo, ValueType arrValueType, const IR::Instr *instr)
{
if (!upperHoistInfo.HasAnyInfo())
{
return;
}
int newOffset = GetBoundCheckOffsetForSimd(arrValueType, instr, upperHoistInfo.Offset());
upperHoistInfo.UpdateOffset(newOffset);
}
#endif
int
GlobOpt::GetBoundCheckOffsetForSimd(ValueType arrValueType, const IR::Instr *instr, const int oldOffset /* = -1 */)
{
#ifdef ENABLE_SIMDJS
if (!(Js::IsSimd128LoadStore(instr->m_opcode)))
{
return oldOffset;
}
if (!arrValueType.IsTypedArray())
{
// no need to adjust for other array types, we will not type-spec (see Simd128DoTypeSpecLoadStore)
return oldOffset;
}
Assert(instr->dataWidth == 4 || instr->dataWidth == 8 || instr->dataWidth == 12 || instr->dataWidth == 16);
int numOfElems = Lowerer::SimdGetElementCountFromBytes(arrValueType, instr->dataWidth);
// we want to make bound checks more conservative. We compute how many extra elements we need to add to the bound check
// e.g. if original bound check is value <= Length + offset, and dataWidth is 16 bytes on Float32 array, then we need room for 4 elements. The bound check guarantees room for 1 element.
// Hence, we need to ensure 3 more: value <= Length + offset - 3
// We round up since dataWidth may span a partial lane (e.g. dataWidth = 12, bpe = 8 bytes)
int offsetBias = -(numOfElems - 1);
// we should always make an existing bound-check more conservative.
Assert(offsetBias <= 0);
return oldOffset + offsetBias;
#else
return oldOffset;
#endif
}
#ifdef ENABLE_SIMDJS
void
GlobOpt::Simd128SetIndirOpndType(IR::IndirOpnd *indirOpnd, Js::OpCode opcode)
{
switch (opcode)
{
case Js::OpCode::Simd128_LdArr_F4:
case Js::OpCode::Simd128_StArr_F4:
indirOpnd->SetType(TySimd128F4);
indirOpnd->SetValueType(ValueType::GetSimd128(ObjectType::Simd128Float32x4));
break;
case Js::OpCode::Simd128_LdArr_I4:
case Js::OpCode::Simd128_StArr_I4:
indirOpnd->SetType(TySimd128I4);
indirOpnd->SetValueType(ValueType::GetSimd128(ObjectType::Simd128Int32x4));
break;
default:
Assert(UNREACHED);
}
}
#endif