Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "jit/BytecodeAnalysis.h"
8
9
#include "jit/JitSpewer.h"
10
#include "vm/BytecodeIterator.h"
11
#include "vm/BytecodeLocation.h"
12
#include "vm/BytecodeUtil.h"
13
14
#include "vm/BytecodeIterator-inl.h"
15
#include "vm/BytecodeLocation-inl.h"
16
#include "vm/BytecodeUtil-inl.h"
17
#include "vm/JSScript-inl.h"
18
19
using namespace js;
20
using namespace js::jit;
21
22
BytecodeAnalysis::BytecodeAnalysis(TempAllocator& alloc, JSScript* script)
23
: script_(script), infos_(alloc), hasTryFinally_(false) {}
24
25
// Bytecode range containing only catch or finally code.
26
struct CatchFinallyRange {
27
uint32_t start; // Inclusive.
28
uint32_t end; // Exclusive.
29
30
CatchFinallyRange(uint32_t start, uint32_t end) : start(start), end(end) {
31
MOZ_ASSERT(end > start);
32
}
33
34
bool contains(uint32_t offset) const {
35
return start <= offset && offset < end;
36
}
37
};
38
39
bool BytecodeAnalysis::init(TempAllocator& alloc) {
40
if (!infos_.growByUninitialized(script_->length())) {
41
return false;
42
}
43
44
// Clear all BytecodeInfo.
45
mozilla::PodZero(infos_.begin(), infos_.length());
46
infos_[0].init(/*stackDepth=*/0);
47
48
Vector<CatchFinallyRange, 0, JitAllocPolicy> catchFinallyRanges(alloc);
49
50
for (const BytecodeLocation& it : AllBytecodesIterable(script_)) {
51
JSOp op = it.getOp();
52
uint32_t offset = it.bytecodeToOffset(script_);
53
54
JitSpew(JitSpew_BaselineOp, "Analyzing op @ %u (end=%u): %s",
55
unsigned(offset), unsigned(script_->length()), CodeName(op));
56
57
// If this bytecode info has not yet been initialized, it's not reachable.
58
if (!infos_[offset].initialized) {
59
continue;
60
}
61
62
uint32_t stackDepth = infos_[offset].stackDepth;
63
64
#ifdef DEBUG
65
size_t endOffset = offset + it.length();
66
for (size_t checkOffset = offset + 1; checkOffset < endOffset;
67
checkOffset++) {
68
MOZ_ASSERT(!infos_[checkOffset].initialized);
69
}
70
#endif
71
uint32_t nuses = it.useCount();
72
uint32_t ndefs = it.defCount();
73
74
MOZ_ASSERT(stackDepth >= nuses);
75
stackDepth -= nuses;
76
stackDepth += ndefs;
77
78
// If stack depth exceeds max allowed by analysis, fail fast.
79
MOZ_ASSERT(stackDepth <= BytecodeInfo::MAX_STACK_DEPTH);
80
81
switch (op) {
82
case JSOp::TableSwitch: {
83
uint32_t defaultOffset = it.getTableSwitchDefaultOffset(script_);
84
int32_t low = it.getTableSwitchLow();
85
int32_t high = it.getTableSwitchHigh();
86
87
infos_[defaultOffset].init(stackDepth);
88
infos_[defaultOffset].jumpTarget = true;
89
90
uint32_t ncases = high - low + 1;
91
92
for (uint32_t i = 0; i < ncases; i++) {
93
uint32_t targetOffset = it.tableSwitchCaseOffset(script_, i);
94
if (targetOffset != defaultOffset) {
95
infos_[targetOffset].init(stackDepth);
96
infos_[targetOffset].jumpTarget = true;
97
}
98
}
99
break;
100
}
101
102
case JSOp::Try: {
103
for (const TryNote& tn : script_->trynotes()) {
104
if (tn.start == offset + JSOpLength_Try &&
105
(tn.kind() == TryNoteKind::Catch ||
106
tn.kind() == TryNoteKind::Finally)) {
107
uint32_t catchOrFinallyOffset = tn.start + tn.length;
108
infos_[catchOrFinallyOffset].init(stackDepth);
109
infos_[catchOrFinallyOffset].jumpTarget = true;
110
}
111
}
112
113
// Get the pc of the last instruction in the try block. It's a
114
// JSOp::Goto to jump over the catch/finally blocks.
115
BytecodeLocation endOfTryLoc(script_,
116
it.toRawBytecode() + it.codeOffset());
117
MOZ_ASSERT(endOfTryLoc.is(JSOp::Goto));
118
119
BytecodeLocation afterTryLoc(
120
script_, endOfTryLoc.toRawBytecode() + endOfTryLoc.jumpOffset());
121
MOZ_ASSERT(afterTryLoc > endOfTryLoc);
122
123
// Ensure the code following the try-block is always marked as
124
// reachable, to simplify Ion's ControlFlowGenerator.
125
uint32_t afterTryOffset = afterTryLoc.bytecodeToOffset(script_);
126
infos_[afterTryOffset].init(stackDepth);
127
infos_[afterTryOffset].jumpTarget = true;
128
129
// Pop CatchFinallyRanges that are no longer needed.
130
while (!catchFinallyRanges.empty() &&
131
catchFinallyRanges.back().end <= offset) {
132
catchFinallyRanges.popBack();
133
}
134
135
CatchFinallyRange range(endOfTryLoc.bytecodeToOffset(script_),
136
afterTryLoc.bytecodeToOffset(script_));
137
if (!catchFinallyRanges.append(range)) {
138
return false;
139
}
140
break;
141
}
142
143
case JSOp::LoopHead:
144
for (size_t i = 0; i < catchFinallyRanges.length(); i++) {
145
if (catchFinallyRanges[i].contains(offset)) {
146
infos_[offset].loopHeadInCatchOrFinally = true;
147
}
148
}
149
break;
150
151
default:
152
break;
153
}
154
155
bool jump = it.isJump();
156
if (jump) {
157
// Case instructions do not push the lvalue back when branching.
158
uint32_t newStackDepth = stackDepth;
159
if (it.is(JSOp::Case)) {
160
newStackDepth--;
161
}
162
163
uint32_t targetOffset = it.getJumpTargetOffset(script_);
164
165
// If this is a backedge, the target JSOp::LoopHead must have been
166
// analyzed already.
167
MOZ_ASSERT_IF(targetOffset < offset, infos_[targetOffset].initialized);
168
169
infos_[targetOffset].init(newStackDepth);
170
infos_[targetOffset].jumpTarget = true;
171
}
172
// Handle any fallthrough from this opcode.
173
if (it.fallsThrough()) {
174
BytecodeLocation fallthroughLoc = it.next();
175
MOZ_ASSERT(fallthroughLoc.isInBounds(script_));
176
uint32_t fallthroughOffset = fallthroughLoc.bytecodeToOffset(script_);
177
178
infos_[fallthroughOffset].init(stackDepth);
179
180
// Treat the fallthrough of a branch instruction as a jump target.
181
if (jump) {
182
infos_[fallthroughOffset].jumpTarget = true;
183
}
184
}
185
}
186
187
// Flag (reachable) resume offset instructions.
188
for (uint32_t offset : script_->resumeOffsets()) {
189
BytecodeInfo& info = infos_[offset];
190
if (info.initialized) {
191
info.hasResumeOffset = true;
192
}
193
}
194
195
return true;
196
}
197
198
IonBytecodeInfo js::jit::AnalyzeBytecodeForIon(JSContext* cx,
199
JSScript* script) {
200
IonBytecodeInfo result;
201
202
if (script->isModule() || script->initialEnvironmentShape() ||
203
(script->function() &&
204
script->function()->needsSomeEnvironmentObject())) {
205
result.usesEnvironmentChain = true;
206
}
207
208
jsbytecode const* pcEnd = script->codeEnd();
209
for (jsbytecode* pc = script->code(); pc < pcEnd; pc = GetNextPc(pc)) {
210
JSOp op = JSOp(*pc);
211
switch (op) {
212
case JSOp::SetArg:
213
result.modifiesArguments = true;
214
break;
215
216
case JSOp::GetName:
217
case JSOp::BindName:
218
case JSOp::BindVar:
219
case JSOp::SetName:
220
case JSOp::StrictSetName:
221
case JSOp::DelName:
222
case JSOp::GetAliasedVar:
223
case JSOp::SetAliasedVar:
224
case JSOp::Lambda:
225
case JSOp::LambdaArrow:
226
case JSOp::DefFun:
227
case JSOp::DefVar:
228
case JSOp::DefLet:
229
case JSOp::DefConst:
230
case JSOp::PushLexicalEnv:
231
case JSOp::PopLexicalEnv:
232
case JSOp::ImplicitThis:
233
case JSOp::FunWithProto:
234
result.usesEnvironmentChain = true;
235
break;
236
237
case JSOp::GetGName:
238
case JSOp::SetGName:
239
case JSOp::StrictSetGName:
240
case JSOp::GImplicitThis:
241
if (script->hasNonSyntacticScope()) {
242
result.usesEnvironmentChain = true;
243
}
244
break;
245
246
case JSOp::Finally:
247
result.hasTryFinally = true;
248
break;
249
250
default:
251
break;
252
}
253
}
254
255
return result;
256
}