Skip to content

Commit 4fef3ad

Browse files
John Portofacebook-github-bot
authored andcommitted
Add ScopeRegisterAnalysis
Summary: Add a new Analysis - ```ScopeRegisterAnalysis```- which is used to map each bytecode to the register containing the Environment that represents the original source-level scope where that instruction "lives". Reviewed By: fbmal7 Differential Revision: D43394136 fbshipit-source-id: 4456e1ad13ad91809631d0ac822ea81c2673e4e1
1 parent 535f951 commit 4fef3ad

File tree

8 files changed

+336
-90
lines changed

8 files changed

+336
-90
lines changed

include/hermes/BCGen/HBC/ISel.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ class HBCISel {
8787
/// and scope depth of each function.
8888
FunctionScopeAnalysis &scopeAnalysis_;
8989

90+
/// The scope register analysis, used to determine which register contains the
91+
/// Environment that's equivalent to the source-level scope where this
92+
/// instruction was emitted.
93+
ScopeRegisterAnalysis &SRA_;
94+
9095
/// For each Basic Block, we map to its beginning instruction location
9196
/// and the next basic block. We need this information to resolve jump
9297
/// targets and exception handler table.
@@ -185,11 +190,13 @@ class HBCISel {
185190
BytecodeFunctionGenerator *BCFGen,
186191
HVMRegisterAllocator &RA,
187192
FunctionScopeAnalysis &scopeAnalysis,
193+
ScopeRegisterAnalysis &SRA,
188194
const BytecodeGenerationOptions &options)
189195
: F_(F),
190196
BCFGen_(BCFGen),
191197
RA_(RA),
192198
scopeAnalysis_(scopeAnalysis),
199+
SRA_(SRA),
193200
bytecodeGenerationOptions_(options) {
194201
protoIdent_ = F->getContext().getIdentifier("__proto__");
195202
}

include/hermes/BCGen/RegAlloc.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class RegisterFile {
123123

124124
class Function;
125125
class Instruction;
126+
class ScopeCreationInst;
126127
class PhiInst;
127128
class CallInst;
128129

@@ -344,6 +345,10 @@ class RegisterAllocator {
344345
/// \returns the computed live interval for the instruction \p I.
345346
Interval &getInstructionInterval(Instruction *I);
346347

348+
/// \return The register assigned to \p Value, if it is available at \p At; or
349+
/// an invalid Register if \p Value is not available at \p At.
350+
Register getRegisterForInstructionAt(Instruction *Value, Instruction *At);
351+
347352
explicit RegisterAllocator(Function *func) : F(func) {}
348353

349354
virtual ~RegisterAllocator() = default;
@@ -416,6 +421,59 @@ class RegisterAllocator {
416421
}
417422
};
418423

424+
/// Analysis for mapping Instructions to the VM register holding its lexical
425+
/// Environment (or the closest one that's available). In the following example
426+
///
427+
/// 1: CreateEnvironment r0
428+
/// 2: LoadConstDouble r1, 1.0
429+
/// 3: LoadConstDouble r2, 2.0
430+
/// 4: AddN r0, r1, r2
431+
/// 5: AddN r0, r0, r0
432+
///
433+
/// registerAndScopeForInstruction(1): invalid Register
434+
/// registerAndScopeForInstruction(2): r0
435+
/// registerAndScopeForInstruction(3): r0
436+
/// registerAndScopeForInstruction(4): r0
437+
/// registerAndScopeForInstruction(5): invalid Register
438+
///
439+
/// This analysis use the register allocation to provide that information, and
440+
/// thus register allocation must be done prior to using
441+
/// registerAndScopeForInstruction. However, the analysis should be created
442+
/// before register allocation is performed. That's necessary as the analysis
443+
/// pre-allocated the environment registers upon initialization in case the code
444+
/// is being compiled with debug information.
445+
class ScopeRegisterAnalysis {
446+
ScopeRegisterAnalysis(const ScopeRegisterAnalysis &) = delete;
447+
void operator=(const ScopeRegisterAnalysis &) = delete;
448+
449+
public:
450+
/// Initializes this scope register analysis object. In full debug info
451+
/// generation mode this constructor will also pre-allocate the environment
452+
/// registers.
453+
explicit ScopeRegisterAnalysis(Function *F, RegisterAllocator &RA);
454+
455+
/// \return a pair with the register and the ScopeDesc for the Environment
456+
/// that's available at \p Inst. This Environment may be the one representing
457+
/// the lexical scope for \p Inst or, if that's not available, one of its
458+
/// parents; if no Environments are available at \p Inst, returns
459+
/// std::pair(invalid register, nullptr ScopeDesc);
460+
std::pair<Register, ScopeDesc *> registerAndScopeForInstruction(
461+
Instruction *Inst);
462+
463+
private:
464+
/// \return a pair with the register and the ScopeDesc for the Environment
465+
/// created by \p SCI if it is available at \p Inst. If it's not available,
466+
/// recursively try to find the closest Environment that's available at
467+
/// \p Inst; if no Environments are available at \p Inst, returns
468+
/// std::pair(invalid register, nullptr ScopeDesc);
469+
std::pair<Register, ScopeDesc *> registerAndScopeAt(
470+
Instruction *Inst,
471+
ScopeCreationInst *SCI);
472+
473+
RegisterAllocator &RA_;
474+
llvh::DenseMap<ScopeDesc *, ScopeCreationInst *> scopeCreationInsts_;
475+
};
476+
419477
} // namespace hermes
420478

421479
namespace llvh {

lib/BCGen/HBC/HBC.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ std::unique_ptr<BytecodeModule> hbc::generateBytecodeModule(
513513
funcGen = BytecodeFunctionGenerator::create(BMGen, 0);
514514
} else {
515515
HVMRegisterAllocator RA(&F);
516+
ScopeRegisterAnalysis SRA(&F, RA);
516517
if (!options.optimizationEnabled) {
517518
RA.setFastPassThreshold(kFastRegisterAllocationThreshold);
518519
RA.setMemoryLimit(kRegisterAllocationMemoryLimit);
@@ -551,7 +552,7 @@ std::unique_ptr<BytecodeModule> hbc::generateBytecodeModule(
551552

552553
funcGen =
553554
BytecodeFunctionGenerator::create(BMGen, RA.getMaxRegisterUsage());
554-
HBCISel hbciSel(&F, funcGen.get(), RA, scopeAnalysis, options);
555+
HBCISel hbciSel(&F, funcGen.get(), RA, scopeAnalysis, SRA, options);
555556
hbciSel.populateDebugCache(debugCache);
556557
hbciSel.generate(sourceMapGen);
557558
debugCache = hbciSel.getDebugCache();

lib/BCGen/RegAlloc.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "llvh/Support/Debug.h"
1717
#include "llvh/Support/raw_ostream.h"
1818

19+
#include <algorithm>
1920
#include <queue>
2021

2122
#define DEBUG_TYPE "regalloc"
@@ -380,6 +381,28 @@ Interval &RegisterAllocator::getInstructionInterval(Instruction *I) {
380381
return instructionInterval_[idx];
381382
}
382383

384+
Register RegisterAllocator::getRegisterForInstructionAt(
385+
Instruction *Value,
386+
Instruction *At) {
387+
if (hasInstructionNumber(Value) && hasInstructionNumber(At)) {
388+
Register valueReg = getRegister(Value);
389+
unsigned loc = getInstructionNumber(At);
390+
if (valueReg.isValid()) {
391+
// Check all of valueReg's intervals, and see if any of those contain loc.
392+
const Interval &i = getInstructionInterval(Value);
393+
auto itBegin = i.segments_.begin(), itEnd = i.segments_.end();
394+
if (std::find_if(itBegin, itEnd, [loc](const Segment &s) {
395+
return s.contains(loc);
396+
})) {
397+
return valueReg;
398+
}
399+
}
400+
}
401+
402+
// Value is not available at At; return an invalid register.
403+
return Register{};
404+
}
405+
383406
bool RegisterAllocator::isManuallyAllocatedInterval(Instruction *I) {
384407
if (hasTargetSpecificLowering(I))
385408
return true;
@@ -926,4 +949,78 @@ bool llvh::DenseMapInfo<Register>::isEqual(Register LHS, Register RHS) {
926949
return LHS.getIndex() == RHS.getIndex();
927950
}
928951

952+
namespace {
953+
static bool preallocateScopeRegisters(const Context &c) {
954+
return c.getDebugInfoSetting() == DebugInfoSetting::ALL;
955+
}
956+
} // namespace
957+
958+
ScopeRegisterAnalysis::ScopeRegisterAnalysis(Function *F, RegisterAllocator &RA)
959+
: RA_(RA) {
960+
// Initialize the ScopeDesc -> ScopeCreationInst map; if emitting full debug
961+
// info, scopeCreationInsts will be used to reserve/pre-allocate the
962+
// environment registers for scopes in F.
963+
llvh::SmallVector<Value *, 4> scopeCreationInsts;
964+
for (BasicBlock &BB : *F) {
965+
for (Instruction &I : BB) {
966+
if (llvh::isa<HBCResolveEnvironment>(I)) {
967+
// HBCResolveEnvironment instructions are used to fetch an environment
968+
// outside of F. The debugger doesn't need access to these "resolved"
969+
// environment.
970+
continue;
971+
} else if (auto *SCI = llvh::dyn_cast<ScopeCreationInst>(&I)) {
972+
assert(
973+
SCI->getCreatedScopeDesc()->getFunction() == F &&
974+
"ScopeCreationInst that is creating a scope of another function");
975+
scopeCreationInsts_[SCI->getCreatedScopeDesc()] = SCI;
976+
if (preallocateScopeRegisters(F->getContext())) {
977+
LLVM_DEBUG(
978+
llvh::dbgs() << "Reserving register for ScopeCreationInst in "
979+
<< F->getOriginalOrInferredName() << "\n");
980+
scopeCreationInsts.push_back(SCI);
981+
}
982+
}
983+
}
984+
}
985+
986+
if (!scopeCreationInsts.empty()) {
987+
RA_.reserve(scopeCreationInsts);
988+
}
989+
}
990+
991+
std::pair<Register, ScopeDesc *> ScopeRegisterAnalysis::registerAndScopeAt(
992+
Instruction *Value,
993+
ScopeCreationInst *SCI) {
994+
// If SCI's value is available in a register, ensure that the register is
995+
// holding SCI's result at loc.
996+
Register sciReg = RA_.getRegisterForInstructionAt(Value, SCI);
997+
if (sciReg.isValid()) {
998+
return std::make_pair(sciReg, SCI->getCreatedScopeDesc());
999+
}
1000+
1001+
// sciReg is not alive at Value, so try to see if SCI's parent scope is.
1002+
auto parentIt =
1003+
scopeCreationInsts_.find(SCI->getCreatedScopeDesc()->getParent());
1004+
if (parentIt == scopeCreationInsts_.end()) {
1005+
// SCI's Parent scope is not available in any register.
1006+
return std::make_pair(Register{}, nullptr);
1007+
}
1008+
1009+
// Try again, this time on SCI's parent Environment.
1010+
return registerAndScopeAt(Value, parentIt->second);
1011+
}
1012+
1013+
std::pair<Register, ScopeDesc *>
1014+
ScopeRegisterAnalysis::registerAndScopeForInstruction(Instruction *Inst) {
1015+
if (ScopeDesc *originalScope = Inst->getSourceLevelScope()) {
1016+
auto sciIt = scopeCreationInsts_.find(originalScope);
1017+
if (sciIt != scopeCreationInsts_.end()) {
1018+
ScopeCreationInst *originalScopeCreation = sciIt->second;
1019+
return registerAndScopeAt(Inst, originalScopeCreation);
1020+
}
1021+
}
1022+
1023+
return std::make_pair(Register{}, nullptr);
1024+
}
1025+
9291026
#undef DEBUG_TYPE

test/BCGen/HBC/debuggercheckbreak.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
//CHECK-NEXT:Offset in debug table: source 0x0000, lexical 0x0000, textified callees 0x0000
1212
//CHECK-NEXT: DeclareGlobalVar "test1"
1313
//CHECK-NEXT: CreateEnvironment r0
14-
//CHECK-NEXT: CreateClosure r1, r0, Function<test1>
15-
//CHECK-NEXT: GetGlobalObject r0
16-
//CHECK-NEXT: PutById r0, r1, 1, "test1"
17-
//CHECK-NEXT: LoadConstUndefined r0
14+
//CHECK-NEXT: CreateClosure r2, r0, Function<test1>
15+
//CHECK-NEXT: GetGlobalObject r1
16+
//CHECK-NEXT: PutById r1, r2, 1, "test1"
17+
//CHECK-NEXT: LoadConstUndefined r1
1818
//CHECK-NEXT: AsyncBreakCheck
19-
//CHECK-NEXT: Ret r0
19+
//CHECK-NEXT: Ret r1
2020

2121
//CHECK-LABEL:Function<test1>(1 params, 16 registers, 0 symbols):
2222
//CHECK-NEXT:Offset in debug table: {{.*}}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
// REQUIRES: slow_debug
9+
10+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O0 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
11+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O0 -g %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=PC
12+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O0 -g0 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
13+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O0 -g1 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
14+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O0 -g2 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
15+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O0 -g3 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=PC
16+
17+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
18+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O -g %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=PC
19+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O -g0 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
20+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O -g1 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
21+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O -g2 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=NOPC
22+
// RUN: %hermesc -target=HBC -fno-inline -debug-only regalloc -dump-bytecode -O -g3 %s 2>&1 | %FileCheck --match-full-lines %s --check-prefix=PC
23+
24+
function sink(n) {
25+
return n;
26+
}
27+
28+
// In No-PreColoring mode (i.e., -g2 or less) there should be no messages about registers being reserved.
29+
// NOPC-NOT: Reserving register for ScopeCreationInst in
30+
31+
print((function _1() {
32+
// PC: Reserving register for ScopeCreationInst in _1
33+
// PC-NOT: Reserving register for ScopeCreationInst in _1
34+
var a = sink(1);
35+
return (function _2() {
36+
// PC: Reserving register for ScopeCreationInst in _2
37+
// PC-NOT: Reserving register for ScopeCreationInst in _2
38+
var b = sink(1);
39+
return (function _3() {
40+
// PC: Reserving register for ScopeCreationInst in _3
41+
// PC-NOT: Reserving register for ScopeCreationInst in _3
42+
var c = sink(1);
43+
return (function _4() {
44+
// PC: Reserving register for ScopeCreationInst in _4
45+
// PC-NOT: Reserving register for ScopeCreationInst in _4
46+
var d = sink(1);
47+
return (function _5() {
48+
// PC: Reserving register for ScopeCreationInst in _5
49+
// PC-NOT: Reserving register for ScopeCreationInst in _5
50+
var e = sink(1);
51+
return (function _6() {
52+
// PC: Reserving register for ScopeCreationInst in _6
53+
// PC-NOT: Reserving register for ScopeCreationInst in _6
54+
var f = sink(1);
55+
return (function _7() {
56+
// PC: Reserving register for ScopeCreationInst in _7
57+
// PC-NOT: Reserving register for ScopeCreationInst in _7
58+
var g = sink(1);
59+
return (function _8() {
60+
// PC: Reserving register for ScopeCreationInst in _8
61+
// PC-NOT: Reserving register for ScopeCreationInst in _8
62+
var h = sink(1);
63+
return (function _9() {
64+
// PC: Reserving register for ScopeCreationInst in _9
65+
// PC-NOT: Reserving register for ScopeCreationInst in _9
66+
var i = sink(1);
67+
return (function _10() {
68+
// PC: Reserving register for ScopeCreationInst in _10
69+
// PC-NOT: Reserving register for ScopeCreationInst in _10
70+
var j = sink(1);
71+
return (function _11() {
72+
return a + b + c + d + e + f + g + h + i + j + k;
73+
})()
74+
})()
75+
})()
76+
})()
77+
})()
78+
})()
79+
})()
80+
})()
81+
})()
82+
})()
83+
})())

0 commit comments

Comments
 (0)