chakra-core / chakra-core/ChakraCore
Potential improvement for virtual typed array access
- Dominant language
- JavaScript
- Stars
- 9.3k
- Forks
- 1.2k
- PR merge metrics
- No merged PRs in 30d
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`:
```js
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[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:
1. 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).
2. 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.
Contributor guide
Assessment
This issue has not been assessed yet.