-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Currently, if we're doing virtual typed array access and don't know whether the offset for the load of an element is necessarily within the 4GB reserved region, we insert bounds checks. However, we seem to do this too often, and in ways that don't give the best perf possible. Consider the following code under -mic:1 -msjrc:1 -bgjit- -oopjit- -dump:globopt
:
let arr = new Uint8Array(0x10000000);
function foo(i) {
i = i | 0;
if(i<0) i = 0;
if(i >= 64) i = 63
return arr[i];
}
foo(0)
foo(1)
foo(2)
for(let i=0;i<100;i++)
{
foo(i);
}
The typed array load in foo generates the following:
Line 4: return arr[i];
Col 5: ^
StatementBoundary #0 #0000
s3[LikelyCanBeTaggedValue_Uint8VirtualArray].var = LdRootFld s6(s1<s7>[Object]->arr)<0,m,++,s7!,s8,{arr(0)}>[LikelyCanBeTaggedValue_Uint8VirtualArray].var! #0000
s9(s2).i32 = FromVar s2[LikelyCanBeTaggedValue_Int].var! #0006 Bailout: #0006 (BailOutIntOnly)
BailOnNotArray s3[LikelyCanBeTaggedValue_Uint8VirtualArray].var #0006 Bailout: #0006 (BailOutOnNotArray)
BoundCheck 0 <= s9(s2).i32 #0006 Bailout: #0006 (BailOutOnArrayAccessHelperCall)
BoundCheck s9(s2).i32 < [s3[Uint8VirtualArray].var+32].u32 #0006 Bailout: #0006 (BailOutOnArrayAccessHelperCall)
BailTarget #0006 Bailout: #0006 (BailOutShared)
Nop #0006
s12(s0).i32 = LdElemI_A [s3[Uint8VirtualArray][><].var!+s9(s2).i32!].var #0006 Bailout: #0006 (BailOutConventionalTypedArrayAccessOnly)
This shows two potential improvements that we can get:
-
For virtual typed arrays, the bounds that we have to check are not the array bounds, but the bounds of the 4GB reserved region. This should be easier to prove statically (currently, even if we have an int range for the index that is within this region, we emit the bounds check).
-
If we can combine the bound check instructions, we can emit better assembly. Currently, these end up lowering as
GLOBOPT INSTR: BoundCheck 0 <= s9(s2).i32 #0006 Bailout: #0006 (BailOutOnArrayAccessHelperCall)
TEST s9(s2).i32, s9(s2).i32 #
JNSB $L12 #
$L13: [helper] #
[s19.u64 < (&BailOutKind)>].u32 = MOV 131072 (0x20000).u32 #
s21.u64 = LEA [s19.u64+XX < (FunctionBody [foo (#1.1), #2])>].u64 #
[s19.u64+XX < (Unknown)>].u64 = MOV s21.u64 #
JMP $L9 #
$L12: #
GLOBOPT INSTR: BoundCheck s9(s2).i32 < [s3[Uint8VirtualArray].var+32].u32 #0006 Bailout: #0006 (BailOutOnArrayAccessHelperCall)
CMP s9(s2).i32, [s3[Uint8VirtualArray].var+32].u32 #
JLT $L10 #
$L11: [helper] #
[s19.u64 < (&BailOutKind)>].u32 = MOV 131072 (0x20000).u32 #
s20.u64 = LEA [s19.u64+XX < (FunctionBody [foo (#1.1), #2])>].u64 #
[s19.u64+XX < (Unknown)>].u64 = MOV s20.u64 #
JMP $L9 #
$L10: #
We can, if the check is changed to be based on the 4GB region, change this to something along the lines of
SAR unused.u64, index.u64, (32-(elementsize-1))
JNZ helper
which saves one copy of the helper and one branch on the critical path.