Merge m-c to fx-team

This commit is contained in:
Joe Walker 2012-11-24 13:53:28 +00:00
commit b351224cce
31 changed files with 1656 additions and 829 deletions

View File

@ -10,19 +10,29 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_FILES = browser_405664.js \
browser_addEngine.js \
browser_contextmenu.js \
testEngine.xml \
testEngine_mozsearch.xml \
testEngine.src \
browser_426329.js \
426329.xml \
browser_483086.js \
483086-1.xml \
483086-2.xml \
test.html \
browser_private_search.js \
$(NULL)
MOCHITEST_BROWSER_FILES = \
browser_405664.js \
browser_addEngine.js \
browser_contextmenu.js \
testEngine.xml \
testEngine_mozsearch.xml \
testEngine.src \
browser_426329.js \
426329.xml \
browser_483086.js \
483086-1.xml \
483086-2.xml \
test.html \
$(NULL)
ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
MOCHITEST_BROWSER_FILES += \
browser_private_search_perwindowpb.js \
$(NULL)
else
MOCHITEST_BROWSER_FILES += \
browser_private_search.js \
$(NULL)
endif
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,115 @@
// This test performs a search in a public window, then a different
// search in a private window, and then checks in the public window
// whether there is an autocomplete entry for the private search.
function test() {
waitForExplicitFinish();
let engineURL =
"http://mochi.test:8888/browser/browser/components/search/test/";
let windowsToClose = [];
registerCleanupFunction(function() {
let engine = Services.search.getEngineByName("Bug 426329");
Services.search.removeEngine(engine);
windowsToClose.forEach(function(win) {
win.close();
});
});
function onPageLoad(aWin, aCallback) {
aWin.gBrowser.addEventListener("DOMContentLoaded", function load(aEvent) {
let doc = aEvent.originalTarget;
info(doc.location.href);
if (doc.location.href.indexOf(engineURL) != -1) {
aWin.gBrowser.removeEventListener("DOMContentLoaded", load, false);
aCallback();
}
}, false);
}
function performSearch(aWin, aIsPrivate, aCallback) {
let searchBar = aWin.BrowserSearch.searchBar;
ok(searchBar, "got search bar");
onPageLoad(aWin, aCallback);
searchBar.value = aIsPrivate ? "private test" : "public test";
searchBar.focus();
EventUtils.synthesizeKey("VK_RETURN", {}, aWin);
}
function addEngine(aCallback) {
function observer(aSub, aTopic, aData) {
switch (aData) {
case "engine-current":
ok(Services.search.currentEngine.name == "Bug 426329",
"currentEngine set");
aCallback();
break;
}
}
Services.obs.addObserver(observer, "browser-search-engine-modified", false);
Services.search.addEngine(
engineURL + "426329.xml", Ci.nsISearchEngine.DATA_XML,
"data:image/x-icon,%00", false);
}
function testOnWindow(aIsPrivate, aCallback) {
let win = OpenBrowserWindow({ private: aIsPrivate });
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
windowsToClose.push(win);
executeSoon(function() aCallback(win));
}, false);
}
addEngine(function() {
testOnWindow(false, function(win) {
performSearch(win, false, function() {
testOnWindow(true, function(win) {
performSearch(win, true, function() {
testOnWindow(false, function(win) {
checkSearchPopup(win, finish);
});
});
});
});
});
});
}
function checkSearchPopup(aWin, aCallback) {
let searchBar = aWin.BrowserSearch.searchBar;
searchBar.value = "p";
searchBar.focus();
let popup = searchBar.textbox.popup;
popup.addEventListener("popupshowing", function showing() {
popup.removeEventListener("popupshowing", showing, false);
let entries = getMenuEntries(searchBar);
for (let i = 0; i < entries.length; i++) {
isnot(entries[0], "private test",
"shouldn't see private autocomplete entries");
}
searchBar.textbox.toggleHistoryPopup();
searchBar.value = "";
aCallback();
}, false);
searchBar.textbox.showHistoryPopup();
}
function getMenuEntries(searchBar) {
let entries = [];
let autocompleteMenu = searchBar.textbox.popup;
// Could perhaps pull values directly from the controller, but it seems
// more reliable to test the values that are actually in the tree?
let column = autocompleteMenu.tree.columns[0];
let numRows = autocompleteMenu.tree.view.rowCount;
for (let i = 0; i < numRows; i++) {
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
}
return entries;
}

View File

@ -194,7 +194,7 @@ void OggCodecState::ReleasePacket(ogg_packet* aPacket) {
delete aPacket;
}
void PacketQueue::Append(ogg_packet* aPacket) {
void OggPacketQueue::Append(ogg_packet* aPacket) {
nsDeque::Push(aPacket);
}

View File

@ -38,7 +38,7 @@
namespace mozilla {
// Deallocates a packet, used in PacketQueue below.
// Deallocates a packet, used in OggPacketQueue below.
class OggPacketDeallocator : public nsDequeFunctor {
virtual void* operator() (void* aPacket) {
ogg_packet* p = static_cast<ogg_packet*>(aPacket);
@ -58,10 +58,10 @@ class OggPacketDeallocator : public nsDequeFunctor {
// frames/samples, reducing the amount of frames/samples we must decode to
// determine start-time at a particular offset, and gives us finer control
// over memory usage.
class PacketQueue : private nsDeque {
class OggPacketQueue : private nsDeque {
public:
PacketQueue() : nsDeque(new OggPacketDeallocator()) {}
~PacketQueue() { Erase(); }
OggPacketQueue() : nsDeque(new OggPacketDeallocator()) {}
~OggPacketQueue() { Erase(); }
bool IsEmpty() { return nsDeque::GetSize() == 0; }
void Append(ogg_packet* aPacket);
ogg_packet* PopFront() { return static_cast<ogg_packet*>(nsDeque::PopFront()); }
@ -167,7 +167,7 @@ public:
// Queue of as yet undecoded packets. Packets are guaranteed to have
// a valid granulepos.
PacketQueue mPackets;
OggPacketQueue mPackets;
// Is the bitstream active; whether we're decoding and playing this bitstream.
bool mActive;

View File

@ -571,6 +571,11 @@ bool OmxDecoder::ReadAudio(AudioFrame *aFrame, int64_t aSeekTimeUs)
return ReadAudio(aFrame, aSeekTimeUs);
}
}
else if (err == ERROR_END_OF_STREAM) {
if (aFrame->mSize == 0) {
return false;
}
}
return true;
}

View File

@ -526,11 +526,11 @@ nsReturnRef<NesteggPacketHolder> WebMReader::NextPacket(TrackType aTrackType)
{
// The packet queue that packets will be pushed on if they
// are not the type we are interested in.
PacketQueue& otherPackets =
WebMPacketQueue& otherPackets =
aTrackType == VIDEO ? mAudioPackets : mVideoPackets;
// The packet queue for the type that we are interested in.
PacketQueue &packets =
WebMPacketQueue &packets =
aTrackType == VIDEO ? mVideoPackets : mAudioPackets;
// Flag to indicate that we do need to playback these types of

View File

@ -62,13 +62,13 @@ class PacketQueueDeallocator : public nsDequeFunctor {
// Typesafe queue for holding nestegg packets. It has
// ownership of the items in the queue and will free them
// when destroyed.
class PacketQueue : private nsDeque {
class WebMPacketQueue : private nsDeque {
public:
PacketQueue()
WebMPacketQueue()
: nsDeque(new PacketQueueDeallocator())
{}
~PacketQueue() {
~WebMPacketQueue() {
Reset();
}
@ -77,12 +77,12 @@ class PacketQueue : private nsDeque {
}
inline void Push(NesteggPacketHolder* aItem) {
NS_ASSERTION(aItem, "NULL pushed to PacketQueue");
NS_ASSERTION(aItem, "NULL pushed to WebMPacketQueue");
nsDeque::Push(aItem);
}
inline void PushFront(NesteggPacketHolder* aItem) {
NS_ASSERTION(aItem, "NULL pushed to PacketQueue");
NS_ASSERTION(aItem, "NULL pushed to WebMPacketQueue");
nsDeque::PushFront(aItem);
}
@ -199,8 +199,8 @@ private:
// Queue of video and audio packets that have been read but not decoded. These
// must only be accessed from the state machine thread.
PacketQueue mVideoPackets;
PacketQueue mAudioPackets;
WebMPacketQueue mVideoPackets;
WebMPacketQueue mAudioPackets;
// Index of video and audio track to play
uint32_t mVideoTrack;

View File

@ -1435,6 +1435,70 @@ CodeGenerator::maybeCreateScriptCounts()
return counts;
}
// Structure for managing the state tracked for a block by script counters.
struct ScriptCountBlockState
{
IonBlockCounts &block;
MacroAssembler &masm;
Sprinter printer;
uint32 instructionBytes;
uint32 spillBytes;
// Pointer to instructionBytes, spillBytes, or NULL, depending on the last
// instruction processed.
uint32 *last;
uint32 lastLength;
public:
ScriptCountBlockState(IonBlockCounts *block, MacroAssembler *masm)
: block(*block), masm(*masm),
printer(GetIonContext()->cx),
instructionBytes(0), spillBytes(0), last(NULL), lastLength(0)
{
}
bool init()
{
if (!printer.init())
return false;
// Bump the hit count for the block at the start. This code is not
// included in either the text for the block or the instruction byte
// counts.
masm.inc64(AbsoluteAddress(block.addressOfHitCount()));
// Collect human readable assembly for the code generated in the block.
masm.setPrinter(&printer);
return true;
}
void visitInstruction(LInstruction *ins)
{
if (last)
*last += masm.size() - lastLength;
lastLength = masm.size();
last = ins->isMoveGroup() ? &spillBytes : &instructionBytes;
// Prefix stream of assembly instructions with their LIR instruction name.
printer.printf("[%s]\n", ins->opName());
}
~ScriptCountBlockState()
{
masm.setPrinter(NULL);
if (last)
*last += masm.size() - lastLength;
block.setCode(printer.string());
block.setInstructionBytes(instructionBytes);
block.setSpillBytes(spillBytes);
}
};
bool
CodeGenerator::generateBody()
{
@ -1451,19 +1515,18 @@ CodeGenerator::generateBody()
return false;
iter++;
mozilla::Maybe<Sprinter> printer;
mozilla::Maybe<ScriptCountBlockState> blockCounts;
if (counts) {
masm.inc64(AbsoluteAddress(counts->block(i).addressOfHitCount()));
printer.construct(GetIonContext()->cx);
if (!printer.ref().init())
blockCounts.construct(&counts->block(i), &masm);
if (!blockCounts.ref().init())
return false;
masm.setPrinter(printer.addr());
}
for (; iter != current->end(); iter++) {
IonSpew(IonSpew_Codegen, "instruction %s", iter->opName());
if (counts)
printer.ref().printf("[%s]\n", iter->opName());
blockCounts.ref().visitInstruction(*iter);
if (iter->safepoint() && pushedArgumentSlots_.length()) {
if (!markArgumentSlots(iter->safepoint()))
@ -1475,11 +1538,6 @@ CodeGenerator::generateBody()
}
if (masm.oom())
return false;
if (counts) {
counts->block(i).setCode(printer.ref().string());
masm.setPrinter(NULL);
}
}
JS_ASSERT(pushedArgumentSlots_.empty());

View File

@ -868,6 +868,17 @@ CompileBackEnd(MIRGenerator *mir)
return NULL;
}
if (js_IonOptions.licm) {
LICM licm(mir, graph);
if (!licm.analyze())
return NULL;
IonSpewPass("LICM");
AssertGraphCoherency(graph);
if (mir->shouldCancel("LICM"))
return NULL;
}
if (js_IonOptions.rangeAnalysis) {
RangeAnalysis r(graph);
if (!r.addBetaNobes())
@ -903,17 +914,6 @@ CompileBackEnd(MIRGenerator *mir)
if (mir->shouldCancel("DCE"))
return NULL;
if (js_IonOptions.licm) {
LICM licm(mir, graph);
if (!licm.analyze())
return NULL;
IonSpewPass("LICM");
AssertGraphCoherency(graph);
if (mir->shouldCancel("LICM"))
return NULL;
}
if (js_IonOptions.edgeCaseAnalysis) {
EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
if (!edgeCaseAnalysis.analyzeLate())

View File

@ -812,7 +812,7 @@ typedef HashMap<uint32,
static HashNumber
BoundsCheckHashIgnoreOffset(MBoundsCheck *check)
{
LinearSum indexSum = ExtractLinearSum(check->index());
SimpleLinearSum indexSum = ExtractLinearSum(check->index());
uintptr_t index = indexSum.term ? uintptr_t(indexSum.term) : 0;
uintptr_t length = uintptr_t(check->length());
return index ^ length;
@ -840,43 +840,105 @@ FindDominatingBoundsCheck(BoundsCheckMap &checks, MBoundsCheck *check, size_t in
}
// Extract a linear sum from ins, if possible (otherwise giving the sum 'ins + 0').
LinearSum
SimpleLinearSum
ion::ExtractLinearSum(MDefinition *ins)
{
if (ins->isBeta())
ins = ins->getOperand(0);
if (ins->type() != MIRType_Int32)
return LinearSum(ins, 0);
return SimpleLinearSum(ins, 0);
if (ins->isConstant()) {
const Value &v = ins->toConstant()->value();
JS_ASSERT(v.isInt32());
return LinearSum(NULL, v.toInt32());
return SimpleLinearSum(NULL, v.toInt32());
} else if (ins->isAdd() || ins->isSub()) {
MDefinition *lhs = ins->getOperand(0);
MDefinition *rhs = ins->getOperand(1);
if (lhs->type() == MIRType_Int32 && rhs->type() == MIRType_Int32) {
LinearSum lsum = ExtractLinearSum(lhs);
LinearSum rsum = ExtractLinearSum(rhs);
SimpleLinearSum lsum = ExtractLinearSum(lhs);
SimpleLinearSum rsum = ExtractLinearSum(rhs);
JS_ASSERT(lsum.term || rsum.term);
if (lsum.term && rsum.term)
return LinearSum(ins, 0);
return SimpleLinearSum(ins, 0);
// Check if this is of the form <SUM> + n, n + <SUM> or <SUM> - n.
if (ins->isAdd()) {
int32 constant;
if (!SafeAdd(lsum.constant, rsum.constant, &constant))
return LinearSum(ins, 0);
return LinearSum(lsum.term ? lsum.term : rsum.term, constant);
return SimpleLinearSum(ins, 0);
return SimpleLinearSum(lsum.term ? lsum.term : rsum.term, constant);
} else if (lsum.term) {
int32 constant;
if (!SafeSub(lsum.constant, rsum.constant, &constant))
return LinearSum(ins, 0);
return LinearSum(lsum.term, constant);
return SimpleLinearSum(ins, 0);
return SimpleLinearSum(lsum.term, constant);
}
}
}
return LinearSum(ins, 0);
return SimpleLinearSum(ins, 0);
}
// Extract a linear inequality holding when a boolean test goes in the
// specified direction, of the form 'lhs + lhsN <= rhs' (or >=).
bool
ion::ExtractLinearInequality(MTest *test, BranchDirection direction,
SimpleLinearSum *plhs, MDefinition **prhs, bool *plessEqual)
{
if (!test->getOperand(0)->isCompare())
return false;
MCompare *compare = test->getOperand(0)->toCompare();
MDefinition *lhs = compare->getOperand(0);
MDefinition *rhs = compare->getOperand(1);
if (compare->specialization() != MIRType_Int32)
return false;
JS_ASSERT(lhs->type() == MIRType_Int32);
JS_ASSERT(rhs->type() == MIRType_Int32);
JSOp jsop = compare->jsop();
if (direction == FALSE_BRANCH)
jsop = analyze::NegateCompareOp(jsop);
SimpleLinearSum lsum = ExtractLinearSum(lhs);
SimpleLinearSum rsum = ExtractLinearSum(rhs);
if (!SafeSub(lsum.constant, rsum.constant, &lsum.constant))
return false;
// Normalize operations to use <= or >=.
switch (jsop) {
case JSOP_LE:
*plessEqual = true;
break;
case JSOP_LT:
/* x < y ==> x + 1 <= y */
if (!SafeAdd(lsum.constant, 1, &lsum.constant))
return false;
*plessEqual = true;
break;
case JSOP_GE:
*plessEqual = false;
break;
case JSOP_GT:
/* x > y ==> x - 1 >= y */
if (!SafeSub(lsum.constant, 1, &lsum.constant))
return false;
*plessEqual = false;
break;
default:
return false;
}
*plhs = lsum;
*prhs = rsum.term;
return true;
}
static bool
@ -889,8 +951,8 @@ TryEliminateBoundsCheck(MBoundsCheck *dominating, MBoundsCheck *dominated, bool
if (dominating->length() != dominated->length())
return true;
LinearSum sumA = ExtractLinearSum(dominating->index());
LinearSum sumB = ExtractLinearSum(dominated->index());
SimpleLinearSum sumA = ExtractLinearSum(dominating->index());
SimpleLinearSum sumB = ExtractLinearSum(dominated->index());
// Both terms should be NULL or the same definition.
if (sumA.term != sumB.term)
@ -1010,3 +1072,86 @@ ion::EliminateRedundantBoundsChecks(MIRGraph &graph)
JS_ASSERT(index == graph.numBlocks());
return true;
}
bool
LinearSum::multiply(int32 scale)
{
for (size_t i = 0; i < terms_.length(); i++) {
if (!SafeMul(scale, terms_[i].scale, &terms_[i].scale))
return false;
}
return SafeMul(scale, constant_, &constant_);
}
bool
LinearSum::add(const LinearSum &other)
{
for (size_t i = 0; i < other.terms_.length(); i++) {
if (!add(other.terms_[i].term, other.terms_[i].scale))
return false;
}
return add(other.constant_);
}
bool
LinearSum::add(MDefinition *term, int32 scale)
{
JS_ASSERT(term);
if (scale == 0)
return true;
if (term->isConstant()) {
int32 constant = term->toConstant()->value().toInt32();
if (!SafeMul(constant, scale, &constant))
return false;
return add(constant);
}
for (size_t i = 0; i < terms_.length(); i++) {
if (term == terms_[i].term) {
if (!SafeAdd(scale, terms_[i].scale, &terms_[i].scale))
return false;
if (terms_[i].scale == 0) {
terms_[i] = terms_.back();
terms_.popBack();
}
return true;
}
}
terms_.append(LinearTerm(term, scale));
return true;
}
bool
LinearSum::add(int32 constant)
{
return SafeAdd(constant, constant_, &constant_);
}
void
LinearSum::print(Sprinter &sp) const
{
for (size_t i = 0; i < terms_.length(); i++) {
int32 scale = terms_[i].scale;
int32 id = terms_[i].term->id();
JS_ASSERT(scale);
if (scale > 0) {
if (i)
sp.printf("+");
if (scale == 1)
sp.printf("#%d", id);
else
sp.printf("%d*#%d", scale, id);
} else if (scale == -1) {
sp.printf("-#%d", id);
} else {
sp.printf("%d*#%d", scale, id);
}
}
if (constant_ > 0)
sp.printf("+%d", constant_);
else if (constant_ < 0)
sp.printf("%d", constant_);
}

View File

@ -11,6 +11,7 @@
// This file declares various analysis passes that operate on MIR.
#include "IonAllocPolicy.h"
#include "MIR.h"
namespace js {
namespace ion {
@ -45,23 +46,69 @@ AssertGraphCoherency(MIRGraph &graph);
bool
EliminateRedundantBoundsChecks(MIRGraph &graph);
// Linear sum of term(s). For now the only linear sums which can be represented
// are 'n' or 'x + n' (for any computation x).
class MDefinition;
struct LinearSum
// Simple linear sum of the form 'n' or 'x + n'.
struct SimpleLinearSum
{
MDefinition *term;
int32 constant;
LinearSum(MDefinition *term, int32 constant)
SimpleLinearSum(MDefinition *term, int32 constant)
: term(term), constant(constant)
{}
};
LinearSum
SimpleLinearSum
ExtractLinearSum(MDefinition *ins);
bool
ExtractLinearInequality(MTest *test, BranchDirection direction,
SimpleLinearSum *plhs, MDefinition **prhs, bool *plessEqual);
struct LinearTerm
{
MDefinition *term;
int32 scale;
LinearTerm(MDefinition *term, int32 scale)
: term(term), scale(scale)
{
}
};
// General linear sum of the form 'x1*n1 + x2*n2 + ... + n'
class LinearSum
{
public:
LinearSum()
: constant_(0)
{
}
LinearSum(const LinearSum &other)
: constant_(other.constant_)
{
for (size_t i = 0; i < other.terms_.length(); i++)
terms_.append(other.terms_[i]);
}
bool multiply(int32 scale);
bool add(const LinearSum &other);
bool add(MDefinition *term, int32 scale);
bool add(int32 constant);
int32 constant() const { return constant_; }
size_t numTerms() const { return terms_.length(); }
LinearTerm term(size_t i) const { return terms_[i]; }
void print(Sprinter &sp) const;
private:
Vector<LinearTerm, 2, IonAllocPolicy> terms_;
int32 constant_;
};
} // namespace ion
} // namespace js

View File

@ -420,9 +420,14 @@ struct IonBlockCounts
// Hit count for this block.
uint64 hitCount_;
// Information about the code generated for this block.
// Text information about the code generated for this block.
char *code_;
// Number of bytes of code generated in this block. Spill code is counted
// separately from other, instruction implementing code.
uint32 instructionBytes_;
uint32 spillBytes_;
public:
bool init(uint32 id, uint32 offset, uint32 numSuccessors) {
@ -485,6 +490,22 @@ struct IonBlockCounts
const char *code() const {
return code_;
}
void setInstructionBytes(uint32 bytes) {
instructionBytes_ = bytes;
}
uint32 instructionBytes() const {
return instructionBytes_;
}
void setSpillBytes(uint32 bytes) {
spillBytes_ = bytes;
}
uint32 spillBytes() const {
return spillBytes_;
}
};
// Execution information for a compiled script which may persist after the

View File

@ -13,6 +13,7 @@
#include "MIR.h"
#include "MIRGraph.h"
#include "LinearScan.h"
#include "RangeAnalysis.h"
using namespace js;
using namespace js::ion;
@ -255,8 +256,14 @@ JSONSpewer::spewMDef(MDefinition *def)
integerValue(use.def()->id());
endList();
stringProperty("type", "%s : [%d, %d]", StringFromMIRType(def->type()),
def->range()->lower(), def->range()->upper());
if (def->range()) {
Sprinter sp(GetIonContext()->cx);
sp.init();
def->range()->print(sp);
stringProperty("type", "%s : %s", sp.string());
} else {
stringProperty("type", "%s", StringFromMIRType(def->type()));
}
if (def->isInstruction()) {
if (MResumePoint *rp = def->toInstruction()->resumePoint())

View File

@ -17,64 +17,6 @@
using namespace js;
using namespace js::ion;
bool
ion::ExtractLinearInequality(MTest *test, BranchDirection direction,
LinearSum *plhs, MDefinition **prhs, bool *plessEqual)
{
if (!test->getOperand(0)->isCompare())
return false;
MCompare *compare = test->getOperand(0)->toCompare();
MDefinition *lhs = compare->getOperand(0);
MDefinition *rhs = compare->getOperand(1);
if (compare->specialization() != MIRType_Int32)
return false;
JS_ASSERT(lhs->type() == MIRType_Int32);
JS_ASSERT(rhs->type() == MIRType_Int32);
JSOp jsop = compare->jsop();
if (direction == FALSE_BRANCH)
jsop = analyze::NegateCompareOp(jsop);
LinearSum lsum = ExtractLinearSum(lhs);
LinearSum rsum = ExtractLinearSum(rhs);
if (!SafeSub(lsum.constant, rsum.constant, &lsum.constant))
return false;
// Normalize operations to use <= or >=.
switch (jsop) {
case JSOP_LE:
*plessEqual = true;
break;
case JSOP_LT:
/* x < y ==> x + 1 <= y */
if (!SafeAdd(lsum.constant, 1, &lsum.constant))
return false;
*plessEqual = true;
break;
case JSOP_GE:
*plessEqual = false;
break;
case JSOP_GT:
/* x > y ==> x - 1 >= y */
if (!SafeSub(lsum.constant, 1, &lsum.constant))
return false;
*plessEqual = false;
break;
default:
return false;
}
*plhs = lsum;
*prhs = rsum.term;
return true;
}
LICM::LICM(MIRGenerator *mir, MIRGraph &graph)
: mir(mir), graph(graph)
{
@ -178,7 +120,6 @@ bool
Loop::optimize()
{
InstructionQueue invariantInstructions;
InstructionQueue boundsChecks;
IonSpew(IonSpew_LICM, "These instructions are in the loop: ");
@ -220,62 +161,17 @@ Loop::optimize()
if (IonSpewEnabled(IonSpew_LICM))
fprintf(IonSpewFile, " Loop Invariant!\n");
} else if (ins->isBoundsCheck()) {
if (!boundsChecks.append(ins))
return false;
}
}
if (!hoistInstructions(invariantInstructions, boundsChecks))
if (!hoistInstructions(invariantInstructions))
return false;
return true;
}
bool
Loop::hoistInstructions(InstructionQueue &toHoist, InstructionQueue &boundsChecks)
Loop::hoistInstructions(InstructionQueue &toHoist)
{
// Hoist bounds checks first, so that hoistBoundsCheck can test for
// invariant instructions, but delay actual insertion until the end to
// handle dependencies on loop invariant instructions.
InstructionQueue hoistedChecks;
for (size_t i = 0; i < boundsChecks.length(); i++) {
MBoundsCheck *ins = boundsChecks[i]->toBoundsCheck();
if (isLoopInvariant(ins) || !isInLoop(ins))
continue;
// Try to find a test dominating the bounds check which can be
// transformed into a hoistable check. Stop after the first such check
// which could be transformed (the one which will be the closest to the
// access in the source).
MBasicBlock *block = ins->block();
while (true) {
BranchDirection direction;
MTest *branch = block->immediateDominatorBranch(&direction);
if (branch) {
MInstruction *upper, *lower;
tryHoistBoundsCheck(ins, branch, direction, &upper, &lower);
if (upper && !hoistedChecks.append(upper))
return false;
if (lower && !hoistedChecks.append(lower))
return false;
if (upper || lower) {
// Note: replace all uses of the original bounds check with the
// actual index. This is usually done during bounds check elimination,
// but in this case it's safe to do it here since the load/store is
// definitely not loop-invariant, so we will never move it before
// one of the bounds checks we just added.
ins->replaceAllUsesWith(ins->index());
ins->block()->discard(ins);
break;
}
}
MBasicBlock *dom = block->immediateDominator();
if (dom == block)
break;
block = dom;
}
}
// Move all instructions to the preLoop_ block just before the control instruction.
for (size_t i = 0; i < toHoist.length(); i++) {
MInstruction *ins = toHoist[i];
@ -292,11 +188,6 @@ Loop::hoistInstructions(InstructionQueue &toHoist, InstructionQueue &boundsCheck
}
}
for (size_t i = 0; i < hoistedChecks.length(); i++) {
MInstruction *ins = hoistedChecks[i];
preLoop_->insertBefore(preLoop_->lastIns(), ins);
}
return true;
}
@ -370,186 +261,3 @@ Loop::popFromWorklist()
toReturn->setNotInWorklist();
return toReturn;
}
// Try to compute hoistable checks for the upper and lower bound on ins,
// according to a test in the loop which dominates ins.
//
// Given a bounds check within a loop which is not loop invariant, we would
// like to compute loop invariant bounds checks which imply that the inner
// check will succeed. These invariant checks can then be added to the
// preheader, and the inner check eliminated.
//
// Example:
//
// for (i = v; i < n; i++)
// x[i] = 0;
//
// There are two constraints captured by the bounds check here: i >= 0, and
// i < length(x). 'i' is not loop invariant, but we can still hoist these
// checks:
//
// - At the point of the check, it is known that i < n. Given this,
// if n <= length(x) then i < length(x), and since n and length(x) are loop
// invariant the former condition can be hoisted and the i < length(x) check
// removed.
//
// - i is only incremented within the loop, so if its initial value is >= 0
// then all its values within the loop will also be >= 0. The lower bounds
// check can be hoisted as v >= 0.
//
// tryHoistBoundsCheck encodes this logic. Given a bounds check B and a test T
// in the loop dominating that bounds check, where B and T share a non-invariant
// term lhs, a new check C is computed such that T && C imply B.
void
Loop::tryHoistBoundsCheck(MBoundsCheck *ins, MTest *test, BranchDirection direction,
MInstruction **pupper, MInstruction **plower)
{
*pupper = NULL;
*plower = NULL;
if (!isLoopInvariant(ins->length()))
return;
LinearSum lhs(NULL, 0);
MDefinition *rhs;
bool lessEqual;
if (!ExtractLinearInequality(test, direction, &lhs, &rhs, &lessEqual))
return;
// Ensure the rhs is a loop invariant term.
if (rhs && !isLoopInvariant(rhs)) {
if (lhs.term && !isLoopInvariant(lhs.term))
return;
MDefinition *temp = lhs.term;
lhs.term = rhs;
rhs = temp;
if (!SafeSub(0, lhs.constant, &lhs.constant))
return;
lessEqual = !lessEqual;
}
JS_ASSERT_IF(rhs, isLoopInvariant(rhs));
// Ensure the lhs is a phi node from the start of the loop body.
if (!lhs.term || !lhs.term->isPhi() || lhs.term->block() != header_)
return;
// Check if the lhs in the conditional matches the bounds check index.
LinearSum index = ExtractLinearSum(ins->index());
if (index.term != lhs.term)
return;
if (!lessEqual)
return;
// At the point of the access, it is known that lhs + lhsN <= rhs, and the
// bounds check is that lhs + indexN + maximum < length. To ensure the
// bounds check holds then, we need to ensure that:
//
// rhs - lhsN + indexN + maximum < length
int32 adjustment;
if (!SafeSub(index.constant, lhs.constant, &adjustment))
return;
if (!SafeAdd(adjustment, ins->maximum(), &adjustment))
return;
// For the lower bound, check that lhs + indexN + minimum >= 0, e.g.
//
// lhs >= -indexN - minimum
//
// lhs is not loop invariant, but if this condition holds of the backing
// variable at loop entry and the variable's value never decreases in the
// loop body, it will hold throughout the loop.
uint32 position = preLoop_->positionInPhiSuccessor();
MDefinition *initialIndex = lhs.term->toPhi()->getOperand(position);
if (!nonDecreasing(initialIndex, lhs.term))
return;
int32 lowerBound;
if (!SafeSub(0, index.constant, &lowerBound))
return;
if (!SafeSub(lowerBound, ins->minimum(), &lowerBound))
return;
// XXX limit on how much can be hoisted, to ensure ballast works?
if (!rhs) {
rhs = MConstant::New(Int32Value(adjustment));
adjustment = 0;
preLoop_->insertBefore(preLoop_->lastIns(), rhs->toInstruction());
}
MBoundsCheck *upper = MBoundsCheck::New(rhs, ins->length());
upper->setMinimum(adjustment);
upper->setMaximum(adjustment);
MBoundsCheckLower *lower = MBoundsCheckLower::New(initialIndex);
lower->setMinimum(lowerBound);
*pupper = upper;
*plower = lower;
}
// Determine whether the possible value of start (a phi node within the loop)
// can become smaller than an initial value at loop entry.
bool
Loop::nonDecreasing(MDefinition *initial, MDefinition *start)
{
MDefinitionVector worklist;
MDefinitionVector seen;
if (!worklist.append(start))
return false;
while (!worklist.empty()) {
MDefinition *def = worklist.popCopy();
bool duplicate = false;
for (size_t i = 0; i < seen.length() && !duplicate; i++) {
if (seen[i] == def)
duplicate = true;
}
if (duplicate)
continue;
if (!seen.append(def))
return false;
if (def->type() != MIRType_Int32)
return false;
if (!isInLoop(def)) {
if (def != initial)
return false;
continue;
}
if (def->isPhi()) {
MPhi *phi = def->toPhi();
for (size_t i = 0; i < phi->numOperands(); i++) {
if (!worklist.append(phi->getOperand(i)))
return false;
}
continue;
}
if (def->isAdd()) {
if (def->toAdd()->specialization() != MIRType_Int32)
return false;
MDefinition *lhs = def->toAdd()->getOperand(0);
MDefinition *rhs = def->toAdd()->getOperand(1);
if (!rhs->isConstant())
return false;
Value v = rhs->toConstant()->value();
if (!v.isInt32() || v.toInt32() < 0)
return false;
if (!worklist.append(lhs))
return false;
continue;
}
return false;
}
return true;
}

View File

@ -33,12 +33,6 @@ class LICM
bool analyze();
};
// Extract a linear inequality holding when a boolean test goes in the
// specified direction, of the form 'lhs + lhsN <= rhs' (or >=).
bool
ExtractLinearInequality(MTest *test, BranchDirection direction,
LinearSum *plhs, MDefinition **prhs, bool *plessEqual);
class Loop
{
MIRGenerator *mir;
@ -77,7 +71,7 @@ class Loop
// Along the way it adds instructions to the worklist for invariance testing.
LoopReturn iterateLoopBlocks(MBasicBlock *current);
bool hoistInstructions(InstructionQueue &toHoist, InstructionQueue &boundsChecks);
bool hoistInstructions(InstructionQueue &toHoist);
// Utility methods for invariance testing and instruction hoisting.
bool isInLoop(MDefinition *ins);
@ -96,15 +90,6 @@ class Loop
inline bool isHoistable(const MDefinition *ins) const {
return ins->isMovable() && !ins->isEffectful();
}
// State for hoisting bounds checks. Even if the terms involved in a bounds
// check are not loop invariant, we analyze the tests and increments done
// in the loop to try to find a stronger condition which can be hoisted.
void tryHoistBoundsCheck(MBoundsCheck *ins, MTest *test, BranchDirection direction,
MInstruction **pupper, MInstruction **plower);
bool nonDecreasing(MDefinition *initial, MDefinition *start);
};
} // namespace ion

View File

@ -10,6 +10,7 @@
#include "MIR.h"
#include "MIRGraph.h"
#include "IonSpewer.h"
#include "RangeAnalysis.h"
#include "jsanalyze.h"
#include "jsbool.h"
#include "jsnum.h"

View File

@ -10,6 +10,7 @@
#include "MIR.h"
#include "MIRGraph.h"
#include "EdgeCaseAnalysis.h"
#include "RangeAnalysis.h"
#include "IonSpewer.h"
#include "jsnum.h"
#include "jsstr.h"
@ -284,11 +285,6 @@ MConstant::MConstant(const js::Value &vp)
{
setResultType(MIRTypeFromValue(vp));
setMovable();
if (type() == MIRType_Int32) {
range()->setLower(value().toInt32());
range()->setUpper(value().toInt32());
}
}
HashNumber
@ -476,53 +472,6 @@ MPhi::addInput(MDefinition *ins)
return inputs_.append(ins);
}
bool
MPhi::recomputeRange()
{
if (type() != MIRType_Int32)
return false;
// Use RangeUpdater rather than Range because it needs to
// track if it has been updated yet.
RangeUpdater r;
JS_ASSERT(getOperand(0)->op() != MDefinition::Op_OsrValue);
bool updated = false;
for (size_t i = 0; i < numOperands(); i++) {
if (getOperand(i)->block()->earlyAbort()) {
IonSpew(IonSpew_Range, "Ignoring unreachable input %d", getOperand(i)->id());
continue;
}
if (!isOSRLikeValue(getOperand(i))) {
if (block()->isLoopHeader()) {
IonSpew(IonSpew_Range, " Updating input #%d (inst %d)", i, getOperand(i)->id());
changeCounts_[i].updateRange(getOperand(i)->range());
r.unionWith(&changeCounts_[i]);
} else {
r.unionWith(getOperand(i)->range());
}
#ifdef DEBUG
if (IonSpewEnabled(IonSpew_Range)) {
fprintf(IonSpewFile, " %d:", getOperand(i)->id());
getOperand(i)->range()->printRange(IonSpewFile);
fprintf(IonSpewFile, " => ");
r.printRange(IonSpewFile);
fprintf(IonSpewFile, "\n");
}
#endif
}
}
if (!updated) {
IonSpew(IonSpew_Range, "My block is unreachable %d", id());
block()->setEarlyAbort();
return false;
}
return range()->update(r.getRange());
}
uint32
MPrepareCall::argc() const
{
@ -894,6 +843,12 @@ MAdd::updateForReplacement(MDefinition *ins_)
return true;
}
bool
MAdd::fallible()
{
return !isTruncated() && (!range() || !range()->isFinite());
}
void
MSub::analyzeTruncateBackward()
{
@ -911,6 +866,12 @@ MSub::updateForReplacement(MDefinition *ins_)
return true;
}
bool
MSub::fallible()
{
return !isTruncated() && (!range() || !range()->isFinite());
}
MDefinition *
MMul::foldsTo(bool useValueNumbers)
{
@ -975,6 +936,24 @@ MMul::updateForReplacement(MDefinition *ins_)
return true;
}
bool
MMul::canOverflow()
{
if (implicitTruncate_)
return false;
return !range() || !range()->isFinite();
}
bool
MMul::canBeNegativeZero()
{
if (!range())
return canBeNegativeZero_;
if (range()->lower() > 0 || range()->upper() < 0)
return false;
return canBeNegativeZero_;
}
void
MBinaryArithInstruction::infer(JSContext *cx, const TypeOracle::BinaryTypes &b)
{
@ -1530,6 +1509,12 @@ MNot::foldsTo(bool useValueNumbers)
return this;
}
bool
MBoundsCheckLower::fallible()
{
return !range() || range()->lower() < minimum_;
}
void
MBeta::printOpcode(FILE *fp)
{
@ -1537,17 +1522,23 @@ MBeta::printOpcode(FILE *fp)
fprintf(fp, " ");
getOperand(0)->printName(fp);
fprintf(fp, " ");
comparison_.printRange(fp);
Sprinter sp(GetIonContext()->cx);
sp.init();
comparison_->print(sp);
fprintf(fp, "%s", sp.string());
}
bool
MBeta::recomputeRange()
void
MBeta::computeRange()
{
bool nullRange = false;
bool ret = range()->update(Range::intersect(val_->range(), &comparison_, &nullRange));
if (nullRange) {
IonSpew(IonSpew_Range, "Marking block for inst %d unexitable", id());
block()->setEarlyAbort();
bool emptyRange = false;
Range *range = Range::intersect(val_->range(), comparison_, &emptyRange);
if (emptyRange) {
IonSpew(IonSpew_Range, "Marking block for inst %d unexitable", id());
block()->setEarlyAbort();
} else {
setRange(range);
}
return ret;
}

View File

@ -24,12 +24,14 @@
#include "IonMacroAssembler.h"
#include "Bailouts.h"
#include "FixedList.h"
#include "RangeAnalysis.h"
#include "CompilerRoot.h"
namespace js {
namespace ion {
class ValueNumberData;
class Range;
static const inline
MIRType MIRTypeFromValue(const js::Value &vp)
{
@ -228,11 +230,7 @@ class MDefinition : public MNode
uint32 id_; // Instruction ID, which after block re-ordering
// is sorted within a basic block.
ValueNumberData *valueNumber_; // The instruction's value number (see GVN for details in use)
// Bug 765126: This should be a pointer. The range should only be allocated if range analysis is
// enabled.
Range range_; // The most specific known range for this def.
Range *range_; // Any computed range for this def.
MIRType resultType_; // Representation of result type.
uint32 flags_; // Bit flags.
union {
@ -292,8 +290,11 @@ class MDefinition : public MNode
return trackedPc_;
}
Range *range() {
return &range_;
Range *range() const {
return range_;
}
void setRange(Range *range) {
range_ = range;
}
virtual HashNumber valueHash() const;
@ -306,9 +307,10 @@ class MDefinition : public MNode
virtual void analyzeEdgeCasesBackward();
virtual void analyzeTruncateBackward();
bool earlyAbortCheck();
// Propagate a range. Return true if the range changed.
virtual bool recomputeRange() {
return false;
// Compute an absolute or symbolic range for the value of this node.
// Ranges are only computed for definitions whose type is int32.
virtual void computeRange() {
}
MNode::Kind kind() const {
@ -623,6 +625,8 @@ class MConstant : public MNullaryInstruction
AliasSet getAliasSet() const {
return AliasSet::None();
}
void computeRange();
};
class MParameter : public MNullaryInstruction
@ -852,6 +856,17 @@ class MGoto : public MAryControlInstruction<0, 1>
}
};
enum BranchDirection {
FALSE_BRANCH,
TRUE_BRANCH
};
static inline BranchDirection
NegateBranchDirection(BranchDirection dir)
{
return (dir == FALSE_BRANCH) ? TRUE_BRANCH : FALSE_BRANCH;
}
// Tests if the input instruction evaluates to true or false, and jumps to the
// start of a corresponding basic block.
class MTest
@ -875,6 +890,9 @@ class MTest
MBasicBlock *ifFalse() const {
return getSuccessor(1);
}
MBasicBlock *branchSuccessor(BranchDirection dir) const {
return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
}
TypePolicy *typePolicy() {
return this;
}
@ -1701,7 +1719,6 @@ class MToInt32 : public MUnaryInstruction
{
setResultType(MIRType_Int32);
setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
}
public:
@ -1742,7 +1759,6 @@ class MTruncateToInt32 : public MUnaryInstruction
{
setResultType(MIRType_Int32);
setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
}
public:
@ -1808,7 +1824,6 @@ class MBitNot
{
setResultType(MIRType_Int32);
setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
}
public:
@ -1904,7 +1919,6 @@ class MBinaryBitwiseInstruction
{
setResultType(MIRType_Int32);
setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
}
public:
@ -1947,12 +1961,7 @@ class MBitAnd : public MBinaryBitwiseInstruction
MDefinition *foldIfEqual() {
return getOperand(0); // x & x => x;
}
bool recomputeRange() {
Range *left = getOperand(0)->range();
Range *right = getOperand(1)->range();
return range()->update(Range::and_(left, right));
}
void computeRange();
};
class MBitOr : public MBinaryBitwiseInstruction
@ -2031,15 +2040,7 @@ class MLsh : public MShiftInstruction
return getOperand(0);
}
bool recomputeRange() {
MDefinition *right = getOperand(1);
if (!right->isConstant())
return false;
int32 c = right->toConstant()->value().toInt32();
const Range *other = getOperand(0)->range();
return range()->update(Range::shl(other, c));
}
void computeRange();
};
class MRsh : public MShiftInstruction
@ -2057,15 +2058,7 @@ class MRsh : public MShiftInstruction
// x >> 0 => x
return getOperand(0);
}
bool recomputeRange() {
MDefinition *right = getOperand(1);
if (!right->isConstant())
return false;
int32 c = right->toConstant()->value().toInt32();
Range *other = getOperand(0)->range();
return range()->update(Range::shr(other, c));
}
void computeRange();
};
class MUrsh : public MShiftInstruction
@ -2140,6 +2133,11 @@ class MBinaryArithInstruction
void infer(JSContext *cx, const TypeOracle::BinaryTypes &b);
void setInt32() {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
}
bool congruentTo(MDefinition *const &ins) const {
return MBinaryInstruction::congruentTo(ins);
}
@ -2226,17 +2224,7 @@ class MAbs
AliasSet getAliasSet() const {
return AliasSet::None();
}
bool recomputeRange() {
if (specialization_ != MIRType_Int32)
return false;
Range *other = getOperand(0)->range();
Range r(0,
Max(Range::abs64((int64_t)other->lower()),
Range::abs64((int64_t)other->upper())));
return range()->update(r);
}
void computeRange();
};
// Inline implementation of Math.sqrt().
@ -2439,18 +2427,8 @@ class MAdd : public MBinaryArithInstruction
return 0;
}
bool fallible() {
return !isTruncated() && !range()->isFinite();
}
bool recomputeRange() {
if (specialization() != MIRType_Int32)
return false;
Range *left = getOperand(0)->range();
Range *right = getOperand(1)->range();
Range next = isTruncated() ? Range::addTruncate(left,right) : Range::add(left, right);
return range()->update(next);
}
bool fallible();
void computeRange();
};
class MSub : public MBinaryArithInstruction
@ -2482,18 +2460,8 @@ class MSub : public MBinaryArithInstruction
return 0;
}
bool fallible() {
return !isTruncated() && !range()->isFinite();
}
bool recomputeRange() {
if (specialization() != MIRType_Int32)
return false;
Range *left = getOperand(0)->range();
Range *right = getOperand(1)->range();
Range next = isTruncated() ? Range::subTruncate(left,right) : Range::sub(left, right);
return range()->update(next);
}
bool fallible();
void computeRange();
};
class MMul : public MBinaryArithInstruction
@ -2540,30 +2508,16 @@ class MMul : public MBinaryArithInstruction
return 1;
}
bool canOverflow() {
return !implicitTruncate_ && !range()->isFinite();
}
bool canOverflow();
bool canBeNegativeZero();
bool canBeNegativeZero() {
if (range()->lower() > 0 || range()->upper() < 0)
return false;
return canBeNegativeZero_;
}
bool updateForReplacement(MDefinition *ins);
bool fallible() {
return canBeNegativeZero_ || canOverflow();
}
bool recomputeRange() {
if (specialization() != MIRType_Int32)
return false;
Range *left = getOperand(0)->range();
Range *right = getOperand(1)->range();
if (isPossibleTruncated())
implicitTruncate_ = !Range::precisionLossMul(left, right);
return range()->update(Range::mul(left, right));
}
void computeRange();
bool isPossibleTruncated() const {
return possibleTruncate_;
@ -2655,21 +2609,7 @@ class MMod : public MBinaryArithInstruction
return 1;
}
bool recomputeRange() {
if (specialization() != MIRType_Int32)
return false;
Range *rhs = getOperand(1)->range();
int64_t a = Range::abs64((int64_t)rhs->lower());
int64_t b = Range::abs64((int64_t)rhs->upper());
if (a ==0 && b == 0) {
// We should never take something % 0.
Range r(INT_MIN, INT_MAX);
return range()->update(r);
}
int64_t bound = Max(1-a, b-1);
Range r(-bound, bound);
return range()->update(r);
}
void computeRange();
};
class MConcat
@ -2709,8 +2649,6 @@ class MCharCodeAt
{
setMovable();
setResultType(MIRType_Int32);
range()->set(0, 65535); //ECMA 262 says that the integer will be
//non-negative and less than 65535.
}
public:
@ -2728,6 +2666,8 @@ class MCharCodeAt
// Strings are immutable, so there is no implicit dependency.
return AliasSet::None();
}
void computeRange();
};
class MFromCharCode
@ -2760,9 +2700,6 @@ class MPhi : public MDefinition, public InlineForwardListNode<MPhi>
bool triedToSpecialize_;
bool hasBytecodeUses_;
bool isIterator_;
// For every input to the phi, track how many times it has changed
// Only used in loop headers, so it defaults to 0 elements to conserve space
js::Vector<RangeChangeCount, 0, IonAllocPolicy> changeCounts_;
MPhi(uint32 slot)
: slot_(slot),
triedToSpecialize_(false),
@ -2819,10 +2756,7 @@ class MPhi : public MDefinition, public InlineForwardListNode<MPhi>
AliasSet getAliasSet() const {
return AliasSet::None();
}
bool recomputeRange();
bool initCounts() {
return changeCounts_.resize(inputs_.length());
}
void computeRange();
};
// The goal of a Beta node is to split a def at a conditionally taken
@ -2830,19 +2764,20 @@ class MPhi : public MDefinition, public InlineForwardListNode<MPhi>
class MBeta : public MUnaryInstruction
{
private:
Range comparison_;
const Range *comparison_;
MDefinition *val_;
MBeta(MDefinition *val, const Range &comp)
MBeta(MDefinition *val, const Range *comp)
: MUnaryInstruction(val),
comparison_(comp),
val_(val)
{
setResultType(val->type());
}
public:
INSTRUCTION_HEADER(Beta);
void printOpcode(FILE *fp);
static MBeta *New(MDefinition *val, const Range &comp)
static MBeta *New(MDefinition *val, const Range *comp)
{
return new MBeta(val, comp);
}
@ -2851,7 +2786,7 @@ class MBeta : public MUnaryInstruction
return AliasSet::None();
}
bool recomputeRange();
void computeRange();
};
// MIR representation of a Value on the OSR StackFrame.
@ -3509,9 +3444,7 @@ class MBoundsCheckLower
AliasSet getAliasSet() const {
return AliasSet::None();
}
bool fallible() {
return range()->lower() < minimum_;
}
bool fallible();
};
// Load a value from a dense array's element vector and does a hole check if the
@ -3987,7 +3920,6 @@ class MClampToUint8
{
setResultType(MIRType_Int32);
setMovable();
range()->set(0, 255);
}
public:
@ -4011,6 +3943,7 @@ class MClampToUint8
AliasSet getAliasSet() const {
return AliasSet::None();
}
void computeRange();
};
class MLoadFixedSlot

View File

@ -30,11 +30,6 @@ typedef InlineForwardListIterator<MPhi> MPhiIterator;
class LBlock;
enum BranchDirection {
FALSE_BRANCH,
TRUE_BRANCH
};
class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock>
{
public:

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
#include "wtf/Platform.h"
#include "MIR.h"
#include "CompileInfo.h"
#include "IonAnalysis.h"
namespace js {
namespace ion {
@ -18,6 +19,52 @@ namespace ion {
class MBasicBlock;
class MIRGraph;
// An upper bound computed on the number of backedges a loop will take.
// This count only includes backedges taken while running Ion code: for OSR
// loops, this will exclude iterations that executed in the interpreter or in
// baseline compiled code.
struct LoopIterationBound : public TempObject
{
// Loop for which this bound applies.
MBasicBlock *header;
// Test from which this bound was derived. Code in the loop body which this
// test dominates (will include the backedge) will execute at most 'bound'
// times. Other code in the loop will execute at most '1 + Max(bound, 0)'
// times.
MTest *test;
// Symbolic bound computed for the number of backedge executions.
LinearSum sum;
LoopIterationBound(MBasicBlock *header, MTest *test, LinearSum sum)
: header(header), test(test), sum(sum)
{
}
};
// A symbolic upper or lower bound computed for a term.
struct SymbolicBound : public TempObject
{
// Any loop iteration bound from which this was derived.
//
// If non-NULL, then 'sum' is only valid within the loop body, at points
// dominated by the loop bound's test (see LoopIterationBound).
//
// If NULL, then 'sum' is always valid.
LoopIterationBound *loop;
// Computed symbolic bound, see above.
LinearSum sum;
SymbolicBound(LoopIterationBound *loop, LinearSum sum)
: loop(loop), sum(sum)
{
}
void print(Sprinter &sp) const;
};
class RangeAnalysis
{
protected:
@ -33,16 +80,20 @@ class RangeAnalysis
bool addBetaNobes();
bool analyze();
bool removeBetaNobes();
private:
void analyzeLoop(MBasicBlock *header);
LoopIterationBound *analyzeLoopIterationCount(MBasicBlock *header,
MTest *test, BranchDirection direction);
void analyzeLoopPhi(MBasicBlock *header, LoopIterationBound *loopBound, MPhi *phi);
bool tryHoistBoundsCheck(MBasicBlock *header, MBoundsCheck *ins);
void markBlocksInLoopBody(MBasicBlock *header, MBasicBlock *current);
};
struct RangeChangeCount;
class Range {
class Range : public TempObject {
private:
// :TODO: we should do symbolic range evaluation, where we have
// information of the form v1 < v2 for arbitrary defs v1 and v2, not
// just constants of type int32.
// (Bug 766592)
// Absolute ranges.
//
// We represent ranges where the endpoints can be in the set:
// {-infty} U [INT_MIN, INT_MAX] U {infty}. A bound of +/-
// infty means that the value may have overflowed in that
@ -69,33 +120,38 @@ class Range {
int32 upper_;
bool upper_infinite_;
// Any symbolic lower or upper bound computed for this term.
const SymbolicBound *symbolicLower_;
const SymbolicBound *symbolicUpper_;
public:
Range()
: lower_(JSVAL_INT_MIN),
lower_infinite_(true),
upper_(JSVAL_INT_MAX),
upper_infinite_(true)
upper_infinite_(true),
symbolicLower_(NULL),
symbolicUpper_(NULL)
{}
Range(int64_t l, int64_t h) {
Range(int64_t l, int64_t h)
: symbolicLower_(NULL),
symbolicUpper_(NULL)
{
setLower(l);
setUpper(h);
}
Range(const Range &other)
: lower_(other.lower_),
lower_infinite_(other.lower_infinite_),
upper_(other.upper_),
upper_infinite_(other.upper_infinite_)
: lower_(other.lower_),
lower_infinite_(other.lower_infinite_),
upper_(other.upper_),
upper_infinite_(other.upper_infinite_),
symbolicLower_(NULL),
symbolicUpper_(NULL)
{}
static Range Truncate(int64_t l, int64_t h) {
Range ret(l,h);
if (!ret.isFinite()) {
ret.makeLowerInfinite();
ret.makeUpperInfinite();
}
return ret;
}
static Range *Truncate(int64_t l, int64_t h);
static int64_t abs64(int64_t x) {
#ifdef WTF_OS_WINDOWS
@ -105,7 +161,7 @@ class Range {
#endif
}
void printRange(FILE *fp);
void print(Sprinter &sp) const;
bool update(const Range *other);
bool update(const Range &other) {
return update(&other);
@ -116,15 +172,15 @@ class Range {
// copying when chaining together unions when handling Phi
// nodes.
void unionWith(const Range *other);
static Range intersect(const Range *lhs, const Range *rhs, bool *nullRange);
static Range addTruncate(const Range *lhs, const Range *rhs);
static Range subTruncate(const Range *lhs, const Range *rhs);
static Range add(const Range *lhs, const Range *rhs);
static Range sub(const Range *lhs, const Range *rhs);
static Range mul(const Range *lhs, const Range *rhs);
static Range and_(const Range *lhs, const Range *rhs);
static Range shl(const Range *lhs, int32 c);
static Range shr(const Range *lhs, int32 c);
static Range * intersect(const Range *lhs, const Range *rhs, bool *emptyRange);
static Range * addTruncate(const Range *lhs, const Range *rhs);
static Range * subTruncate(const Range *lhs, const Range *rhs);
static Range * add(const Range *lhs, const Range *rhs);
static Range * sub(const Range *lhs, const Range *rhs);
static Range * mul(const Range *lhs, const Range *rhs);
static Range * and_(const Range *lhs, const Range *rhs);
static Range * shl(const Range *lhs, int32 c);
static Range * shr(const Range *lhs, int32 c);
static bool precisionLossMul(const Range *lhs, const Range *rhs);
@ -184,35 +240,20 @@ class Range {
setLower(l);
setUpper(h);
}
};
struct RangeChangeCount {
Range oldRange;
unsigned char lowerCount_ : 4;
unsigned char upperCount_ : 4;
RangeChangeCount() : oldRange(), lowerCount_(0), upperCount_(0) {};
void updateRange(Range *newRange) {
JS_ASSERT(newRange->lower() >= oldRange.lower());
if (newRange->lower() != oldRange.lower())
lowerCount_ = lowerCount_ < 15 ? lowerCount_ + 1 : lowerCount_;
JS_ASSERT(newRange->upper() <= oldRange.upper());
if (newRange->upper() != oldRange.upper())
upperCount_ = upperCount_ < 15 ? upperCount_ + 1 : upperCount_;
oldRange = *newRange;
const SymbolicBound *symbolicLower() const {
return symbolicLower_;
}
const SymbolicBound *symbolicUpper() const {
return symbolicUpper_;
}
void setSymbolicLower(SymbolicBound *bound) {
symbolicLower_ = bound;
}
void setSymbolicUpper(SymbolicBound *bound) {
symbolicUpper_ = bound;
}
};
class RangeUpdater {
Range r_;
bool lowerSet_;
bool upperSet_;
public:
RangeUpdater() : r_(), lowerSet_(false), upperSet_(false) {}
void unionWith(const Range *other);
void unionWith(RangeChangeCount *other);
void updateLower(const Range * other);
void updateUpper(const Range * other);
Range *getRange() { JS_ASSERT(lowerSet_ && upperSet_); return &r_; }
void printRange(FILE *fp) { r_.printRange(fp); }
};
} // namespace ion

View File

@ -2008,7 +2008,7 @@ JITCodeHasCheck(HandleScript script, jsbytecode *pc, RecompileKind kind)
}
#endif
if (script->hasAnyIonScript())
if (script->hasAnyIonScript() || script->isIonCompilingOffThread())
return false;
return true;

View File

@ -328,7 +328,8 @@ js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp)
Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
for (size_t j = 0; j < block.numSuccessors(); j++)
Sprint(sp, " -> #%lu", block.successor(j));
Sprint(sp, " :: %llu hits\n", block.hitCount());
Sprint(sp, " :: %llu hits %u instruction bytes %u spill bytes\n",
block.hitCount(), block.instructionBytes(), block.spillBytes());
Sprint(sp, "%s\n", block.code());
}
ionCounts = ionCounts->previous();
@ -7209,6 +7210,12 @@ GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf)
return false;
buf.append(str);
AppendJSONProperty(buf, "instructionBytes");
NumberValueToStringBuffer(cx, Int32Value(block.instructionBytes()), buf);
AppendJSONProperty(buf, "spillBytes");
NumberValueToStringBuffer(cx, Int32Value(block.spillBytes()), buf);
buf.append('}');
}
buf.append(']');

View File

@ -113,7 +113,7 @@ class AudioSendAndReceive
{
public:
static const unsigned int PLAYOUT_SAMPLE_FREQUENCY; //default is 16000
static const unsigned int PLAYOUT_SAMPLE_LENGTH; //default is 160
static const unsigned int PLAYOUT_SAMPLE_LENGTH; //default is 160000
AudioSendAndReceive()
{
@ -150,7 +150,7 @@ private:
};
const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_FREQUENCY = 16000;
const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_LENGTH = 160;
const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_LENGTH = 160000;
int AudioSendAndReceive::WriteWaveHeader(int rate, int channels, FILE* outFile)
{
@ -271,7 +271,7 @@ void AudioSendAndReceive::GenerateAndReadSamples()
int16_t audioOutput[PLAYOUT_SAMPLE_LENGTH];
short* inbuf;
int sampleLengthDecoded = 0;
int SAMPLES = (PLAYOUT_SAMPLE_FREQUENCY * 10)/1000; //10 milliseconds
int SAMPLES = (PLAYOUT_SAMPLE_FREQUENCY * 10); //10 seconds
int CHANNELS = 1; //mono audio
int sampleLengthInBytes = sizeof(audioInput);
//generated audio buffer
@ -329,7 +329,7 @@ void AudioSendAndReceive::GenerateAndReadSamples()
cerr << "Couldn't Write " << sampleLengthInBytes << "bytes" << endl;
break;
}
}while(numSamplesReadFromInput <= (SAMPLES));
}while(numSamplesReadFromInput < SAMPLES);
FinishWaveHeader(outFile);
fclose(outFile);

View File

@ -0,0 +1,41 @@
package org.mozilla.gecko;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.CheckBox;
import android.widget.Checkable;
import android.widget.LinearLayout;
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private CheckBox mCheckBox;
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean isChecked() {
return mCheckBox != null ? mCheckBox.isChecked() : false;
}
public void setChecked(boolean isChecked) {
if (mCheckBox != null)
mCheckBox.setChecked(isChecked);
}
public void toggle() {
if (mCheckBox != null)
mCheckBox.toggle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCheckBox = (CheckBox) findViewById(R.id.checkbox);
mCheckBox.setClickable(false);
}
}

View File

@ -27,6 +27,7 @@ import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@ -82,10 +83,12 @@ import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.AbsoluteLayout;
import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
@ -1108,14 +1111,14 @@ abstract public class GeckoApp
/**
* @param aPermissions
* Array of JSON objects to represent site permissions.
* Example: { type: "offline-app", setting: "Store Offline Data: Allow" }
* Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" }
*/
private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null);
((TextView) customTitleView.findViewById(R.id.title)).setText(R.string.site_settings_title);
((TextView) customTitleView.findViewById(R.id.host)).setText(aHost);
((TextView) customTitleView.findViewById(R.id.host)).setText(aHost);
builder.setCustomTitle(customTitleView);
// If there are no permissions to clear, show the user a message about that.
@ -1123,24 +1126,32 @@ abstract public class GeckoApp
if (aPermissions.length() == 0) {
builder.setMessage(R.string.site_settings_no_settings);
} else {
// Eventually we should use a list adapter and custom checkable list items
// to make a two-line UI to match the mock-ups
CharSequence[] items = new CharSequence[aPermissions.length()];
boolean[] states = new boolean[aPermissions.length()];
ArrayList <HashMap<String, String>> itemList = new ArrayList <HashMap<String, String>>();
for (int i = 0; i < aPermissions.length(); i++) {
try {
items[i] = aPermissions.getJSONObject(i).getString("setting");
// Make all the items checked by default.
states[i] = true;
JSONObject permObj = aPermissions.getJSONObject(i);
HashMap<String, String> map = new HashMap<String, String>();
map.put("setting", permObj.getString("setting"));
map.put("value", permObj.getString("value"));
itemList.add(map);
} catch (JSONException e) {
Log.w(LOGTAG, "Exception populating settings items.", e);
}
}
builder.setMultiChoiceItems(items, states, new DialogInterface.OnMultiChoiceClickListener(){
public void onClick(DialogInterface dialog, int item, boolean state) {
// Do nothing
}
});
// setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
// setSingleChoiceItems and changing the choiceMode below when we create the dialog
builder.setSingleChoiceItems(new SimpleAdapter(
GeckoApp.this,
itemList,
R.layout.site_setting_item,
new String[] { "setting", "value" },
new int[] { R.id.setting, R.id.value }
), -1, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { }
});
builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
ListView listView = ((AlertDialog) dialog).getListView();
@ -1148,12 +1159,12 @@ abstract public class GeckoApp
// An array of the indices of the permissions we want to clear
JSONArray permissionsToClear = new JSONArray();
for (int i = 0; i < checkedItemPositions.size(); i++) {
boolean checked = checkedItemPositions.get(i);
if (checked)
for (int i = 0; i < checkedItemPositions.size(); i++)
if (checkedItemPositions.get(i))
permissionsToClear.put(i);
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Clear", permissionsToClear.toString()));
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
"Permissions:Clear", permissionsToClear.toString()));
}
});
}
@ -1161,12 +1172,21 @@ abstract public class GeckoApp
builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
}
});
mMainHandler.post(new Runnable() {
public void run() {
builder.create().show();
Dialog dialog = builder.create();
dialog.show();
ListView listView = ((AlertDialog) dialog).getListView();
if (listView != null) {
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
int listSize = listView.getAdapter().getCount();
for (int i = 0; i < listSize; i++)
listView.setItemChecked(i, true);
}
}
});
}

View File

@ -57,6 +57,7 @@ FENNEC_JAVA_FILES = \
CameraImageResultHandler.java \
CameraVideoResultHandler.java \
CanvasDelegate.java \
CheckableLinearLayout.java \
SyncPreference.java \
db/BrowserDB.java \
db/LocalBrowserDB.java \
@ -364,6 +365,7 @@ RES_LAYOUT = \
res/layout/notification_icon_text.xml \
res/layout/notification_progress.xml \
res/layout/notification_progress_text.xml \
res/layout/site_setting_item.xml \
res/layout/site_setting_title.xml \
res/layout/setup_screen.xml \
res/layout/shared_ui_components.xml \

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<org.mozilla.gecko.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="16dip"
android:paddingRight="12dip"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:focusable="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_vertical"
android:focusable="false">
<TextView
android:id="@+id/setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorAlertDialogListItem"
android:gravity="center_vertical|left"
android:singleLine="true"
android:ellipsize="marquee"/>
<TextView
android:id="@+id/value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorAlertDialogListItem"
android:gravity="center_vertical|left"
android:singleLine="true"
android:ellipsize="marquee"/>
</LinearLayout>
<CheckBox
android:id="@+id/checkbox"
android:layout_width="35dip"
android:layout_height="wrap_content"
android:paddingRight="12dip"
android:gravity="center_vertical"
android:focusable="false"
android:clickable="false"/>
</org.mozilla.gecko.CheckableLinearLayout>

View File

@ -6011,18 +6011,15 @@ var PermissionsHelper = {
"allowed" : "denied";
let valueString = Strings.browser.GetStringFromName(typeStrings[valueKey]);
// If we implement a two-line UI, we will need to pass the label and
// value individually and let java handle the formatting
let setting = Strings.browser.formatStringFromName("siteSettings.labelToValue",
[ label, valueString ], 2);
permissions.push({
type: type,
setting: setting
setting: label,
value: valueString
});
}
// Keep track of permissions, so we know which ones to clear
this._currentPermissions = permissions;
this._currentPermissions = permissions;
let host;
try {

View File

@ -7,6 +7,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <prlong.h>
#include <prprf.h>
#include <prtime.h>
#include "nsProfileLock.h"
#ifdef XP_WIN
@ -122,6 +125,8 @@ private:
NS_HIDDEN_(nsresult) Init();
nsresult CreateTimesInternal(nsIFile *profileDir);
nsresult CreateProfileInternal(nsIFile* aRootDir,
nsIFile* aLocalDir,
const nsACString& aName,
@ -813,6 +818,12 @@ nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
NS_ENSURE_SUCCESS(rv, rv);
}
// We created a new profile dir. Let's store a creation timestamp.
// Note that this code path does not apply if the profile dir was
// created prior to launching.
rv = CreateTimesInternal(rootDir);
NS_ENSURE_SUCCESS(rv, rv);
nsToolkitProfile* last = aForExternalApp ? nullptr : mFirst;
if (last) {
while (last->mNext)
@ -827,6 +838,40 @@ nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
return NS_OK;
}
nsresult
nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir)
{
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIFile> creationLog;
rv = aProfileDir->Clone(getter_AddRefs(creationLog));
NS_ENSURE_SUCCESS(rv, rv);
rv = creationLog->AppendNative(NS_LITERAL_CSTRING("times.json"));
NS_ENSURE_SUCCESS(rv, rv);
bool exists = false;
creationLog->Exists(&exists);
if (exists) {
return NS_OK;
}
rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
NS_ENSURE_SUCCESS(rv, rv);
// We don't care about microsecond resolution.
PRInt64 msec;
LL_DIV(msec, PR_Now(), PR_USEC_PER_MSEC);
// Write it out.
PRFileDesc *writeFile;
rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
NS_ENSURE_SUCCESS(rv, rv);
PR_fprintf(writeFile, "{\n\"created\": %lld\n}\n", msec);
PR_Close(writeFile);
return NS_OK;
}
NS_IMETHODIMP
nsToolkitProfileService::GetProfileCount(uint32_t *aResult)
{

View File

@ -29,8 +29,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=543854
const ASCIIName = "myprofile";
const UnicodeName = "\u09A0\u09BE\u0995\u09C1\u09B0"; // A Bengali name
var gIOService;
var gProfileService;
gIOService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
gProfileService = Cc["@mozilla.org/toolkit/profile-service;1"].
getService(Ci.nsIToolkitProfileService);
@ -38,24 +42,72 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=543854
createProfile(UnicodeName);
SimpleTest.finish();
function createProfile(profileName)
{
var profile = gProfileService.createProfile(null, null, profileName);
/**
* Read the contents of an nsIFile. Throws on error.
* @param file an nsIFile instance.
* @return string contents.
*/
function readFile(file) {
let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
let sstream = Cc["@mozilla.org/scriptableinputstream;1"].
createInstance(Components.interfaces.nsIScriptableInputStream);
const RO = 0x01;
const READ_OTHERS = 4;
fstream.init(file, RO, READ_OTHERS, 0);
sstream.init(fstream);
let out = sstream.read(sstream.available());
sstream.close();
fstream.close();
return out;
}
function checkBounds(lowerBound, value, upperBound) {
ok(lowerBound <= value, "value " + value +
" is above lower bound " + lowerBound);
ok(upperBound >= value, "value " + value +
" is within upper bound " + upperBound);
}
function createProfile(profileName) {
// Filesystem precision is lower than Date precision.
let lowerBound = Date.now() - 1000;
let profile = gProfileService.createProfile(null, null, profileName);
// check that the directory was created
isnot(profile, null, "Profile " + profileName + " created");
var profileDir = profile.rootDir;
let profileDir = profile.rootDir;
ok(profileDir.exists(), "Profile dir created");
ok(profileDir.isDirectory(), "Profile dir is a directory");
var profileDirPath = profileDir.path;
let profileDirPath = profileDir.path;
is(profileDirPath.substr(profileDirPath.length - profileName.length),
profileName, "Profile dir has expected name");
// clean up the profile
// Ensure that our timestamp file was created.
let jsonFile = profileDir.clone();
jsonFile.append("times.json");
ok(jsonFile.path, "Path is " + jsonFile.path);
ok(jsonFile.exists(), "Times file was created");
ok(jsonFile.isFile(), "Times file is a file");
let json = JSON.parse(readFile(jsonFile));
let upperBound = Date.now() + 1000;
let created = json.created;
ok(created, "created is set");
// Check against real clock time.
checkBounds(lowerBound, created, upperBound);
// Clean up the profile.
profile.remove(true);
}