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 include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_FILES = browser_405664.js \ MOCHITEST_BROWSER_FILES = \
browser_addEngine.js \ browser_405664.js \
browser_contextmenu.js \ browser_addEngine.js \
testEngine.xml \ browser_contextmenu.js \
testEngine_mozsearch.xml \ testEngine.xml \
testEngine.src \ testEngine_mozsearch.xml \
browser_426329.js \ testEngine.src \
426329.xml \ browser_426329.js \
browser_483086.js \ 426329.xml \
483086-1.xml \ browser_483086.js \
483086-2.xml \ 483086-1.xml \
test.html \ 483086-2.xml \
browser_private_search.js \ test.html \
$(NULL) $(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 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; delete aPacket;
} }
void PacketQueue::Append(ogg_packet* aPacket) { void OggPacketQueue::Append(ogg_packet* aPacket) {
nsDeque::Push(aPacket); nsDeque::Push(aPacket);
} }

View File

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

View File

@ -571,6 +571,11 @@ bool OmxDecoder::ReadAudio(AudioFrame *aFrame, int64_t aSeekTimeUs)
return ReadAudio(aFrame, aSeekTimeUs); return ReadAudio(aFrame, aSeekTimeUs);
} }
} }
else if (err == ERROR_END_OF_STREAM) {
if (aFrame->mSize == 0) {
return false;
}
}
return true; 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 // The packet queue that packets will be pushed on if they
// are not the type we are interested in. // are not the type we are interested in.
PacketQueue& otherPackets = WebMPacketQueue& otherPackets =
aTrackType == VIDEO ? mAudioPackets : mVideoPackets; aTrackType == VIDEO ? mAudioPackets : mVideoPackets;
// The packet queue for the type that we are interested in. // The packet queue for the type that we are interested in.
PacketQueue &packets = WebMPacketQueue &packets =
aTrackType == VIDEO ? mVideoPackets : mAudioPackets; aTrackType == VIDEO ? mVideoPackets : mAudioPackets;
// Flag to indicate that we do need to playback these types of // 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 // Typesafe queue for holding nestegg packets. It has
// ownership of the items in the queue and will free them // ownership of the items in the queue and will free them
// when destroyed. // when destroyed.
class PacketQueue : private nsDeque { class WebMPacketQueue : private nsDeque {
public: public:
PacketQueue() WebMPacketQueue()
: nsDeque(new PacketQueueDeallocator()) : nsDeque(new PacketQueueDeallocator())
{} {}
~PacketQueue() { ~WebMPacketQueue() {
Reset(); Reset();
} }
@ -77,12 +77,12 @@ class PacketQueue : private nsDeque {
} }
inline void Push(NesteggPacketHolder* aItem) { inline void Push(NesteggPacketHolder* aItem) {
NS_ASSERTION(aItem, "NULL pushed to PacketQueue"); NS_ASSERTION(aItem, "NULL pushed to WebMPacketQueue");
nsDeque::Push(aItem); nsDeque::Push(aItem);
} }
inline void PushFront(NesteggPacketHolder* aItem) { inline void PushFront(NesteggPacketHolder* aItem) {
NS_ASSERTION(aItem, "NULL pushed to PacketQueue"); NS_ASSERTION(aItem, "NULL pushed to WebMPacketQueue");
nsDeque::PushFront(aItem); nsDeque::PushFront(aItem);
} }
@ -199,8 +199,8 @@ private:
// Queue of video and audio packets that have been read but not decoded. These // Queue of video and audio packets that have been read but not decoded. These
// must only be accessed from the state machine thread. // must only be accessed from the state machine thread.
PacketQueue mVideoPackets; WebMPacketQueue mVideoPackets;
PacketQueue mAudioPackets; WebMPacketQueue mAudioPackets;
// Index of video and audio track to play // Index of video and audio track to play
uint32_t mVideoTrack; uint32_t mVideoTrack;

View File

@ -1435,6 +1435,70 @@ CodeGenerator::maybeCreateScriptCounts()
return counts; 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 bool
CodeGenerator::generateBody() CodeGenerator::generateBody()
{ {
@ -1451,19 +1515,18 @@ CodeGenerator::generateBody()
return false; return false;
iter++; iter++;
mozilla::Maybe<Sprinter> printer; mozilla::Maybe<ScriptCountBlockState> blockCounts;
if (counts) { if (counts) {
masm.inc64(AbsoluteAddress(counts->block(i).addressOfHitCount())); blockCounts.construct(&counts->block(i), &masm);
printer.construct(GetIonContext()->cx); if (!blockCounts.ref().init())
if (!printer.ref().init())
return false; return false;
masm.setPrinter(printer.addr());
} }
for (; iter != current->end(); iter++) { for (; iter != current->end(); iter++) {
IonSpew(IonSpew_Codegen, "instruction %s", iter->opName()); IonSpew(IonSpew_Codegen, "instruction %s", iter->opName());
if (counts) if (counts)
printer.ref().printf("[%s]\n", iter->opName()); blockCounts.ref().visitInstruction(*iter);
if (iter->safepoint() && pushedArgumentSlots_.length()) { if (iter->safepoint() && pushedArgumentSlots_.length()) {
if (!markArgumentSlots(iter->safepoint())) if (!markArgumentSlots(iter->safepoint()))
@ -1475,11 +1538,6 @@ CodeGenerator::generateBody()
} }
if (masm.oom()) if (masm.oom())
return false; return false;
if (counts) {
counts->block(i).setCode(printer.ref().string());
masm.setPrinter(NULL);
}
} }
JS_ASSERT(pushedArgumentSlots_.empty()); JS_ASSERT(pushedArgumentSlots_.empty());

View File

@ -868,6 +868,17 @@ CompileBackEnd(MIRGenerator *mir)
return NULL; 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) { if (js_IonOptions.rangeAnalysis) {
RangeAnalysis r(graph); RangeAnalysis r(graph);
if (!r.addBetaNobes()) if (!r.addBetaNobes())
@ -903,17 +914,6 @@ CompileBackEnd(MIRGenerator *mir)
if (mir->shouldCancel("DCE")) if (mir->shouldCancel("DCE"))
return NULL; 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) { if (js_IonOptions.edgeCaseAnalysis) {
EdgeCaseAnalysis edgeCaseAnalysis(mir, graph); EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
if (!edgeCaseAnalysis.analyzeLate()) if (!edgeCaseAnalysis.analyzeLate())

View File

@ -812,7 +812,7 @@ typedef HashMap<uint32,
static HashNumber static HashNumber
BoundsCheckHashIgnoreOffset(MBoundsCheck *check) 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 index = indexSum.term ? uintptr_t(indexSum.term) : 0;
uintptr_t length = uintptr_t(check->length()); uintptr_t length = uintptr_t(check->length());
return index ^ 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'). // Extract a linear sum from ins, if possible (otherwise giving the sum 'ins + 0').
LinearSum SimpleLinearSum
ion::ExtractLinearSum(MDefinition *ins) ion::ExtractLinearSum(MDefinition *ins)
{ {
if (ins->isBeta())
ins = ins->getOperand(0);
if (ins->type() != MIRType_Int32) if (ins->type() != MIRType_Int32)
return LinearSum(ins, 0); return SimpleLinearSum(ins, 0);
if (ins->isConstant()) { if (ins->isConstant()) {
const Value &v = ins->toConstant()->value(); const Value &v = ins->toConstant()->value();
JS_ASSERT(v.isInt32()); JS_ASSERT(v.isInt32());
return LinearSum(NULL, v.toInt32()); return SimpleLinearSum(NULL, v.toInt32());
} else if (ins->isAdd() || ins->isSub()) { } else if (ins->isAdd() || ins->isSub()) {
MDefinition *lhs = ins->getOperand(0); MDefinition *lhs = ins->getOperand(0);
MDefinition *rhs = ins->getOperand(1); MDefinition *rhs = ins->getOperand(1);
if (lhs->type() == MIRType_Int32 && rhs->type() == MIRType_Int32) { if (lhs->type() == MIRType_Int32 && rhs->type() == MIRType_Int32) {
LinearSum lsum = ExtractLinearSum(lhs); SimpleLinearSum lsum = ExtractLinearSum(lhs);
LinearSum rsum = ExtractLinearSum(rhs); SimpleLinearSum rsum = ExtractLinearSum(rhs);
JS_ASSERT(lsum.term || rsum.term);
if (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. // Check if this is of the form <SUM> + n, n + <SUM> or <SUM> - n.
if (ins->isAdd()) { if (ins->isAdd()) {
int32 constant; int32 constant;
if (!SafeAdd(lsum.constant, rsum.constant, &constant)) if (!SafeAdd(lsum.constant, rsum.constant, &constant))
return LinearSum(ins, 0); return SimpleLinearSum(ins, 0);
return LinearSum(lsum.term ? lsum.term : rsum.term, constant); return SimpleLinearSum(lsum.term ? lsum.term : rsum.term, constant);
} else if (lsum.term) { } else if (lsum.term) {
int32 constant; int32 constant;
if (!SafeSub(lsum.constant, rsum.constant, &constant)) if (!SafeSub(lsum.constant, rsum.constant, &constant))
return LinearSum(ins, 0); return SimpleLinearSum(ins, 0);
return LinearSum(lsum.term, constant); 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 static bool
@ -889,8 +951,8 @@ TryEliminateBoundsCheck(MBoundsCheck *dominating, MBoundsCheck *dominated, bool
if (dominating->length() != dominated->length()) if (dominating->length() != dominated->length())
return true; return true;
LinearSum sumA = ExtractLinearSum(dominating->index()); SimpleLinearSum sumA = ExtractLinearSum(dominating->index());
LinearSum sumB = ExtractLinearSum(dominated->index()); SimpleLinearSum sumB = ExtractLinearSum(dominated->index());
// Both terms should be NULL or the same definition. // Both terms should be NULL or the same definition.
if (sumA.term != sumB.term) if (sumA.term != sumB.term)
@ -1010,3 +1072,86 @@ ion::EliminateRedundantBoundsChecks(MIRGraph &graph)
JS_ASSERT(index == graph.numBlocks()); JS_ASSERT(index == graph.numBlocks());
return true; 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. // This file declares various analysis passes that operate on MIR.
#include "IonAllocPolicy.h" #include "IonAllocPolicy.h"
#include "MIR.h"
namespace js { namespace js {
namespace ion { namespace ion {
@ -45,23 +46,69 @@ AssertGraphCoherency(MIRGraph &graph);
bool bool
EliminateRedundantBoundsChecks(MIRGraph &graph); 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; class MDefinition;
struct LinearSum // Simple linear sum of the form 'n' or 'x + n'.
struct SimpleLinearSum
{ {
MDefinition *term; MDefinition *term;
int32 constant; int32 constant;
LinearSum(MDefinition *term, int32 constant) SimpleLinearSum(MDefinition *term, int32 constant)
: term(term), constant(constant) : term(term), constant(constant)
{} {}
}; };
LinearSum SimpleLinearSum
ExtractLinearSum(MDefinition *ins); 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 ion
} // namespace js } // namespace js

View File

@ -420,9 +420,14 @@ struct IonBlockCounts
// Hit count for this block. // Hit count for this block.
uint64 hitCount_; uint64 hitCount_;
// Information about the code generated for this block. // Text information about the code generated for this block.
char *code_; 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: public:
bool init(uint32 id, uint32 offset, uint32 numSuccessors) { bool init(uint32 id, uint32 offset, uint32 numSuccessors) {
@ -485,6 +490,22 @@ struct IonBlockCounts
const char *code() const { const char *code() const {
return code_; 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 // Execution information for a compiled script which may persist after the

View File

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

View File

@ -17,64 +17,6 @@
using namespace js; using namespace js;
using namespace js::ion; 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) LICM::LICM(MIRGenerator *mir, MIRGraph &graph)
: mir(mir), graph(graph) : mir(mir), graph(graph)
{ {
@ -178,7 +120,6 @@ bool
Loop::optimize() Loop::optimize()
{ {
InstructionQueue invariantInstructions; InstructionQueue invariantInstructions;
InstructionQueue boundsChecks;
IonSpew(IonSpew_LICM, "These instructions are in the loop: "); IonSpew(IonSpew_LICM, "These instructions are in the loop: ");
@ -220,62 +161,17 @@ Loop::optimize()
if (IonSpewEnabled(IonSpew_LICM)) if (IonSpewEnabled(IonSpew_LICM))
fprintf(IonSpewFile, " Loop Invariant!\n"); 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 false;
return true; return true;
} }
bool 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. // Move all instructions to the preLoop_ block just before the control instruction.
for (size_t i = 0; i < toHoist.length(); i++) { for (size_t i = 0; i < toHoist.length(); i++) {
MInstruction *ins = toHoist[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; return true;
} }
@ -370,186 +261,3 @@ Loop::popFromWorklist()
toReturn->setNotInWorklist(); toReturn->setNotInWorklist();
return toReturn; 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(); 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 class Loop
{ {
MIRGenerator *mir; MIRGenerator *mir;
@ -77,7 +71,7 @@ class Loop
// Along the way it adds instructions to the worklist for invariance testing. // Along the way it adds instructions to the worklist for invariance testing.
LoopReturn iterateLoopBlocks(MBasicBlock *current); LoopReturn iterateLoopBlocks(MBasicBlock *current);
bool hoistInstructions(InstructionQueue &toHoist, InstructionQueue &boundsChecks); bool hoistInstructions(InstructionQueue &toHoist);
// Utility methods for invariance testing and instruction hoisting. // Utility methods for invariance testing and instruction hoisting.
bool isInLoop(MDefinition *ins); bool isInLoop(MDefinition *ins);
@ -96,15 +90,6 @@ class Loop
inline bool isHoistable(const MDefinition *ins) const { inline bool isHoistable(const MDefinition *ins) const {
return ins->isMovable() && !ins->isEffectful(); 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 } // namespace ion

View File

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

View File

@ -10,6 +10,7 @@
#include "MIR.h" #include "MIR.h"
#include "MIRGraph.h" #include "MIRGraph.h"
#include "EdgeCaseAnalysis.h" #include "EdgeCaseAnalysis.h"
#include "RangeAnalysis.h"
#include "IonSpewer.h" #include "IonSpewer.h"
#include "jsnum.h" #include "jsnum.h"
#include "jsstr.h" #include "jsstr.h"
@ -284,11 +285,6 @@ MConstant::MConstant(const js::Value &vp)
{ {
setResultType(MIRTypeFromValue(vp)); setResultType(MIRTypeFromValue(vp));
setMovable(); setMovable();
if (type() == MIRType_Int32) {
range()->setLower(value().toInt32());
range()->setUpper(value().toInt32());
}
} }
HashNumber HashNumber
@ -476,53 +472,6 @@ MPhi::addInput(MDefinition *ins)
return inputs_.append(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 uint32
MPrepareCall::argc() const MPrepareCall::argc() const
{ {
@ -894,6 +843,12 @@ MAdd::updateForReplacement(MDefinition *ins_)
return true; return true;
} }
bool
MAdd::fallible()
{
return !isTruncated() && (!range() || !range()->isFinite());
}
void void
MSub::analyzeTruncateBackward() MSub::analyzeTruncateBackward()
{ {
@ -911,6 +866,12 @@ MSub::updateForReplacement(MDefinition *ins_)
return true; return true;
} }
bool
MSub::fallible()
{
return !isTruncated() && (!range() || !range()->isFinite());
}
MDefinition * MDefinition *
MMul::foldsTo(bool useValueNumbers) MMul::foldsTo(bool useValueNumbers)
{ {
@ -975,6 +936,24 @@ MMul::updateForReplacement(MDefinition *ins_)
return true; 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 void
MBinaryArithInstruction::infer(JSContext *cx, const TypeOracle::BinaryTypes &b) MBinaryArithInstruction::infer(JSContext *cx, const TypeOracle::BinaryTypes &b)
{ {
@ -1530,6 +1509,12 @@ MNot::foldsTo(bool useValueNumbers)
return this; return this;
} }
bool
MBoundsCheckLower::fallible()
{
return !range() || range()->lower() < minimum_;
}
void void
MBeta::printOpcode(FILE *fp) MBeta::printOpcode(FILE *fp)
{ {
@ -1537,17 +1522,23 @@ MBeta::printOpcode(FILE *fp)
fprintf(fp, " "); fprintf(fp, " ");
getOperand(0)->printName(fp); getOperand(0)->printName(fp);
fprintf(fp, " "); fprintf(fp, " ");
comparison_.printRange(fp);
Sprinter sp(GetIonContext()->cx);
sp.init();
comparison_->print(sp);
fprintf(fp, "%s", sp.string());
} }
bool void
MBeta::recomputeRange() MBeta::computeRange()
{ {
bool nullRange = false; bool emptyRange = false;
bool ret = range()->update(Range::intersect(val_->range(), &comparison_, &nullRange));
if (nullRange) { Range *range = Range::intersect(val_->range(), comparison_, &emptyRange);
IonSpew(IonSpew_Range, "Marking block for inst %d unexitable", id()); if (emptyRange) {
block()->setEarlyAbort(); 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 "IonMacroAssembler.h"
#include "Bailouts.h" #include "Bailouts.h"
#include "FixedList.h" #include "FixedList.h"
#include "RangeAnalysis.h"
#include "CompilerRoot.h" #include "CompilerRoot.h"
namespace js { namespace js {
namespace ion { namespace ion {
class ValueNumberData; class ValueNumberData;
class Range;
static const inline static const inline
MIRType MIRTypeFromValue(const js::Value &vp) MIRType MIRTypeFromValue(const js::Value &vp)
{ {
@ -228,11 +230,7 @@ class MDefinition : public MNode
uint32 id_; // Instruction ID, which after block re-ordering uint32 id_; // Instruction ID, which after block re-ordering
// is sorted within a basic block. // is sorted within a basic block.
ValueNumberData *valueNumber_; // The instruction's value number (see GVN for details in use) ValueNumberData *valueNumber_; // The instruction's value number (see GVN for details in use)
Range *range_; // Any computed range for this def.
// 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.
MIRType resultType_; // Representation of result type. MIRType resultType_; // Representation of result type.
uint32 flags_; // Bit flags. uint32 flags_; // Bit flags.
union { union {
@ -292,8 +290,11 @@ class MDefinition : public MNode
return trackedPc_; return trackedPc_;
} }
Range *range() { Range *range() const {
return &range_; return range_;
}
void setRange(Range *range) {
range_ = range;
} }
virtual HashNumber valueHash() const; virtual HashNumber valueHash() const;
@ -306,9 +307,10 @@ class MDefinition : public MNode
virtual void analyzeEdgeCasesBackward(); virtual void analyzeEdgeCasesBackward();
virtual void analyzeTruncateBackward(); virtual void analyzeTruncateBackward();
bool earlyAbortCheck(); bool earlyAbortCheck();
// Propagate a range. Return true if the range changed.
virtual bool recomputeRange() { // Compute an absolute or symbolic range for the value of this node.
return false; // Ranges are only computed for definitions whose type is int32.
virtual void computeRange() {
} }
MNode::Kind kind() const { MNode::Kind kind() const {
@ -623,6 +625,8 @@ class MConstant : public MNullaryInstruction
AliasSet getAliasSet() const { AliasSet getAliasSet() const {
return AliasSet::None(); return AliasSet::None();
} }
void computeRange();
}; };
class MParameter : public MNullaryInstruction 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 // Tests if the input instruction evaluates to true or false, and jumps to the
// start of a corresponding basic block. // start of a corresponding basic block.
class MTest class MTest
@ -875,6 +890,9 @@ class MTest
MBasicBlock *ifFalse() const { MBasicBlock *ifFalse() const {
return getSuccessor(1); return getSuccessor(1);
} }
MBasicBlock *branchSuccessor(BranchDirection dir) const {
return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
}
TypePolicy *typePolicy() { TypePolicy *typePolicy() {
return this; return this;
} }
@ -1701,7 +1719,6 @@ class MToInt32 : public MUnaryInstruction
{ {
setResultType(MIRType_Int32); setResultType(MIRType_Int32);
setMovable(); setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
} }
public: public:
@ -1742,7 +1759,6 @@ class MTruncateToInt32 : public MUnaryInstruction
{ {
setResultType(MIRType_Int32); setResultType(MIRType_Int32);
setMovable(); setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
} }
public: public:
@ -1808,7 +1824,6 @@ class MBitNot
{ {
setResultType(MIRType_Int32); setResultType(MIRType_Int32);
setMovable(); setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
} }
public: public:
@ -1904,7 +1919,6 @@ class MBinaryBitwiseInstruction
{ {
setResultType(MIRType_Int32); setResultType(MIRType_Int32);
setMovable(); setMovable();
range()->set(JSVAL_INT_MIN, JSVAL_INT_MAX);
} }
public: public:
@ -1947,12 +1961,7 @@ class MBitAnd : public MBinaryBitwiseInstruction
MDefinition *foldIfEqual() { MDefinition *foldIfEqual() {
return getOperand(0); // x & x => x; return getOperand(0); // x & x => x;
} }
bool recomputeRange() { void computeRange();
Range *left = getOperand(0)->range();
Range *right = getOperand(1)->range();
return range()->update(Range::and_(left, right));
}
}; };
class MBitOr : public MBinaryBitwiseInstruction class MBitOr : public MBinaryBitwiseInstruction
@ -2031,15 +2040,7 @@ class MLsh : public MShiftInstruction
return getOperand(0); return getOperand(0);
} }
bool recomputeRange() { void computeRange();
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));
}
}; };
class MRsh : public MShiftInstruction class MRsh : public MShiftInstruction
@ -2057,15 +2058,7 @@ class MRsh : public MShiftInstruction
// x >> 0 => x // x >> 0 => x
return getOperand(0); return getOperand(0);
} }
bool recomputeRange() { void computeRange();
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));
}
}; };
class MUrsh : public MShiftInstruction class MUrsh : public MShiftInstruction
@ -2140,6 +2133,11 @@ class MBinaryArithInstruction
void infer(JSContext *cx, const TypeOracle::BinaryTypes &b); void infer(JSContext *cx, const TypeOracle::BinaryTypes &b);
void setInt32() {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
}
bool congruentTo(MDefinition *const &ins) const { bool congruentTo(MDefinition *const &ins) const {
return MBinaryInstruction::congruentTo(ins); return MBinaryInstruction::congruentTo(ins);
} }
@ -2226,17 +2224,7 @@ class MAbs
AliasSet getAliasSet() const { AliasSet getAliasSet() const {
return AliasSet::None(); return AliasSet::None();
} }
bool recomputeRange() { void computeRange();
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);
}
}; };
// Inline implementation of Math.sqrt(). // Inline implementation of Math.sqrt().
@ -2439,18 +2427,8 @@ class MAdd : public MBinaryArithInstruction
return 0; return 0;
} }
bool fallible() { bool fallible();
return !isTruncated() && !range()->isFinite(); void computeRange();
}
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);
}
}; };
class MSub : public MBinaryArithInstruction class MSub : public MBinaryArithInstruction
@ -2482,18 +2460,8 @@ class MSub : public MBinaryArithInstruction
return 0; return 0;
} }
bool fallible() { bool fallible();
return !isTruncated() && !range()->isFinite(); void computeRange();
}
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);
}
}; };
class MMul : public MBinaryArithInstruction class MMul : public MBinaryArithInstruction
@ -2540,30 +2508,16 @@ class MMul : public MBinaryArithInstruction
return 1; return 1;
} }
bool canOverflow() { bool canOverflow();
return !implicitTruncate_ && !range()->isFinite(); bool canBeNegativeZero();
}
bool canBeNegativeZero() {
if (range()->lower() > 0 || range()->upper() < 0)
return false;
return canBeNegativeZero_;
}
bool updateForReplacement(MDefinition *ins); bool updateForReplacement(MDefinition *ins);
bool fallible() { bool fallible() {
return canBeNegativeZero_ || canOverflow(); return canBeNegativeZero_ || canOverflow();
} }
bool recomputeRange() { void computeRange();
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));
}
bool isPossibleTruncated() const { bool isPossibleTruncated() const {
return possibleTruncate_; return possibleTruncate_;
@ -2655,21 +2609,7 @@ class MMod : public MBinaryArithInstruction
return 1; return 1;
} }
bool recomputeRange() { void computeRange();
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);
}
}; };
class MConcat class MConcat
@ -2709,8 +2649,6 @@ class MCharCodeAt
{ {
setMovable(); setMovable();
setResultType(MIRType_Int32); setResultType(MIRType_Int32);
range()->set(0, 65535); //ECMA 262 says that the integer will be
//non-negative and less than 65535.
} }
public: public:
@ -2728,6 +2666,8 @@ class MCharCodeAt
// Strings are immutable, so there is no implicit dependency. // Strings are immutable, so there is no implicit dependency.
return AliasSet::None(); return AliasSet::None();
} }
void computeRange();
}; };
class MFromCharCode class MFromCharCode
@ -2760,9 +2700,6 @@ class MPhi : public MDefinition, public InlineForwardListNode<MPhi>
bool triedToSpecialize_; bool triedToSpecialize_;
bool hasBytecodeUses_; bool hasBytecodeUses_;
bool isIterator_; 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) MPhi(uint32 slot)
: slot_(slot), : slot_(slot),
triedToSpecialize_(false), triedToSpecialize_(false),
@ -2819,10 +2756,7 @@ class MPhi : public MDefinition, public InlineForwardListNode<MPhi>
AliasSet getAliasSet() const { AliasSet getAliasSet() const {
return AliasSet::None(); return AliasSet::None();
} }
bool recomputeRange(); void computeRange();
bool initCounts() {
return changeCounts_.resize(inputs_.length());
}
}; };
// The goal of a Beta node is to split a def at a conditionally taken // 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 class MBeta : public MUnaryInstruction
{ {
private: private:
Range comparison_; const Range *comparison_;
MDefinition *val_; MDefinition *val_;
MBeta(MDefinition *val, const Range &comp) MBeta(MDefinition *val, const Range *comp)
: MUnaryInstruction(val), : MUnaryInstruction(val),
comparison_(comp), comparison_(comp),
val_(val) val_(val)
{ {
setResultType(val->type());
} }
public: public:
INSTRUCTION_HEADER(Beta); INSTRUCTION_HEADER(Beta);
void printOpcode(FILE *fp); 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); return new MBeta(val, comp);
} }
@ -2851,7 +2786,7 @@ class MBeta : public MUnaryInstruction
return AliasSet::None(); return AliasSet::None();
} }
bool recomputeRange(); void computeRange();
}; };
// MIR representation of a Value on the OSR StackFrame. // MIR representation of a Value on the OSR StackFrame.
@ -3509,9 +3444,7 @@ class MBoundsCheckLower
AliasSet getAliasSet() const { AliasSet getAliasSet() const {
return AliasSet::None(); return AliasSet::None();
} }
bool fallible() { bool fallible();
return range()->lower() < minimum_;
}
}; };
// Load a value from a dense array's element vector and does a hole check if the // 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); setResultType(MIRType_Int32);
setMovable(); setMovable();
range()->set(0, 255);
} }
public: public:
@ -4011,6 +3943,7 @@ class MClampToUint8
AliasSet getAliasSet() const { AliasSet getAliasSet() const {
return AliasSet::None(); return AliasSet::None();
} }
void computeRange();
}; };
class MLoadFixedSlot class MLoadFixedSlot

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
#include "wtf/Platform.h" #include "wtf/Platform.h"
#include "MIR.h" #include "MIR.h"
#include "CompileInfo.h" #include "CompileInfo.h"
#include "IonAnalysis.h"
namespace js { namespace js {
namespace ion { namespace ion {
@ -18,6 +19,52 @@ namespace ion {
class MBasicBlock; class MBasicBlock;
class MIRGraph; 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 class RangeAnalysis
{ {
protected: protected:
@ -33,16 +80,20 @@ class RangeAnalysis
bool addBetaNobes(); bool addBetaNobes();
bool analyze(); bool analyze();
bool removeBetaNobes(); 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 : public TempObject {
class Range {
private: private:
// :TODO: we should do symbolic range evaluation, where we have // Absolute ranges.
// information of the form v1 < v2 for arbitrary defs v1 and v2, not //
// just constants of type int32.
// (Bug 766592)
// We represent ranges where the endpoints can be in the set: // We represent ranges where the endpoints can be in the set:
// {-infty} U [INT_MIN, INT_MAX] U {infty}. A bound of +/- // {-infty} U [INT_MIN, INT_MAX] U {infty}. A bound of +/-
// infty means that the value may have overflowed in that // infty means that the value may have overflowed in that
@ -69,33 +120,38 @@ class Range {
int32 upper_; int32 upper_;
bool upper_infinite_; bool upper_infinite_;
// Any symbolic lower or upper bound computed for this term.
const SymbolicBound *symbolicLower_;
const SymbolicBound *symbolicUpper_;
public: public:
Range() Range()
: lower_(JSVAL_INT_MIN), : lower_(JSVAL_INT_MIN),
lower_infinite_(true), lower_infinite_(true),
upper_(JSVAL_INT_MAX), 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); setLower(l);
setUpper(h); setUpper(h);
} }
Range(const Range &other) Range(const Range &other)
: lower_(other.lower_), : lower_(other.lower_),
lower_infinite_(other.lower_infinite_), lower_infinite_(other.lower_infinite_),
upper_(other.upper_), upper_(other.upper_),
upper_infinite_(other.upper_infinite_) upper_infinite_(other.upper_infinite_),
symbolicLower_(NULL),
symbolicUpper_(NULL)
{} {}
static Range Truncate(int64_t l, int64_t h) {
Range ret(l,h); static Range *Truncate(int64_t l, int64_t h);
if (!ret.isFinite()) {
ret.makeLowerInfinite();
ret.makeUpperInfinite();
}
return ret;
}
static int64_t abs64(int64_t x) { static int64_t abs64(int64_t x) {
#ifdef WTF_OS_WINDOWS #ifdef WTF_OS_WINDOWS
@ -105,7 +161,7 @@ class Range {
#endif #endif
} }
void printRange(FILE *fp); void print(Sprinter &sp) const;
bool update(const Range *other); bool update(const Range *other);
bool update(const Range &other) { bool update(const Range &other) {
return update(&other); return update(&other);
@ -116,15 +172,15 @@ class Range {
// copying when chaining together unions when handling Phi // copying when chaining together unions when handling Phi
// nodes. // nodes.
void unionWith(const Range *other); void unionWith(const Range *other);
static Range intersect(const Range *lhs, const Range *rhs, bool *nullRange); static Range * intersect(const Range *lhs, const Range *rhs, bool *emptyRange);
static Range addTruncate(const Range *lhs, const Range *rhs); static Range * addTruncate(const Range *lhs, const Range *rhs);
static Range subTruncate(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 * add(const Range *lhs, const Range *rhs);
static Range sub(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 * mul(const Range *lhs, const Range *rhs);
static Range and_(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 * shl(const Range *lhs, int32 c);
static Range shr(const Range *lhs, int32 c); static Range * shr(const Range *lhs, int32 c);
static bool precisionLossMul(const Range *lhs, const Range *rhs); static bool precisionLossMul(const Range *lhs, const Range *rhs);
@ -184,35 +240,20 @@ class Range {
setLower(l); setLower(l);
setUpper(h); setUpper(h);
} }
};
struct RangeChangeCount { const SymbolicBound *symbolicLower() const {
Range oldRange; return symbolicLower_;
unsigned char lowerCount_ : 4; }
unsigned char upperCount_ : 4; const SymbolicBound *symbolicUpper() const {
RangeChangeCount() : oldRange(), lowerCount_(0), upperCount_(0) {}; return symbolicUpper_;
void updateRange(Range *newRange) { }
JS_ASSERT(newRange->lower() >= oldRange.lower());
if (newRange->lower() != oldRange.lower()) void setSymbolicLower(SymbolicBound *bound) {
lowerCount_ = lowerCount_ < 15 ? lowerCount_ + 1 : lowerCount_; symbolicLower_ = bound;
JS_ASSERT(newRange->upper() <= oldRange.upper()); }
if (newRange->upper() != oldRange.upper()) void setSymbolicUpper(SymbolicBound *bound) {
upperCount_ = upperCount_ < 15 ? upperCount_ + 1 : upperCount_; symbolicUpper_ = bound;
oldRange = *newRange;
} }
};
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 } // namespace ion

View File

@ -2008,7 +2008,7 @@ JITCodeHasCheck(HandleScript script, jsbytecode *pc, RecompileKind kind)
} }
#endif #endif
if (script->hasAnyIonScript()) if (script->hasAnyIonScript() || script->isIonCompilingOffThread())
return false; return false;
return true; 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()); Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
for (size_t j = 0; j < block.numSuccessors(); j++) for (size_t j = 0; j < block.numSuccessors(); j++)
Sprint(sp, " -> #%lu", block.successor(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()); Sprint(sp, "%s\n", block.code());
} }
ionCounts = ionCounts->previous(); ionCounts = ionCounts->previous();
@ -7209,6 +7210,12 @@ GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf)
return false; return false;
buf.append(str); 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('}');
} }
buf.append(']'); buf.append(']');

View File

@ -113,7 +113,7 @@ class AudioSendAndReceive
{ {
public: public:
static const unsigned int PLAYOUT_SAMPLE_FREQUENCY; //default is 16000 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() AudioSendAndReceive()
{ {
@ -150,7 +150,7 @@ private:
}; };
const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_FREQUENCY = 16000; 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) int AudioSendAndReceive::WriteWaveHeader(int rate, int channels, FILE* outFile)
{ {
@ -271,7 +271,7 @@ void AudioSendAndReceive::GenerateAndReadSamples()
int16_t audioOutput[PLAYOUT_SAMPLE_LENGTH]; int16_t audioOutput[PLAYOUT_SAMPLE_LENGTH];
short* inbuf; short* inbuf;
int sampleLengthDecoded = 0; 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 CHANNELS = 1; //mono audio
int sampleLengthInBytes = sizeof(audioInput); int sampleLengthInBytes = sizeof(audioInput);
//generated audio buffer //generated audio buffer
@ -329,7 +329,7 @@ void AudioSendAndReceive::GenerateAndReadSamples()
cerr << "Couldn't Write " << sampleLengthInBytes << "bytes" << endl; cerr << "Couldn't Write " << sampleLengthInBytes << "bytes" << endl;
break; break;
} }
}while(numSamplesReadFromInput <= (SAMPLES)); }while(numSamplesReadFromInput < SAMPLES);
FinishWaveHeader(outFile); FinishWaveHeader(outFile);
fclose(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.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -82,10 +83,12 @@ import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.widget.AbsoluteLayout; import android.widget.AbsoluteLayout;
import android.widget.CheckBox;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.SimpleAdapter;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -1108,14 +1111,14 @@ abstract public class GeckoApp
/** /**
* @param aPermissions * @param aPermissions
* Array of JSON objects to represent site permissions. * 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) { private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this); final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null); 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.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); builder.setCustomTitle(customTitleView);
// If there are no permissions to clear, show the user a message about that. // 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) { if (aPermissions.length() == 0) {
builder.setMessage(R.string.site_settings_no_settings); builder.setMessage(R.string.site_settings_no_settings);
} else { } else {
// Eventually we should use a list adapter and custom checkable list items
// to make a two-line UI to match the mock-ups ArrayList <HashMap<String, String>> itemList = new ArrayList <HashMap<String, String>>();
CharSequence[] items = new CharSequence[aPermissions.length()];
boolean[] states = new boolean[aPermissions.length()];
for (int i = 0; i < aPermissions.length(); i++) { for (int i = 0; i < aPermissions.length(); i++) {
try { try {
items[i] = aPermissions.getJSONObject(i).getString("setting"); JSONObject permObj = aPermissions.getJSONObject(i);
// Make all the items checked by default. HashMap<String, String> map = new HashMap<String, String>();
states[i] = true; map.put("setting", permObj.getString("setting"));
map.put("value", permObj.getString("value"));
itemList.add(map);
} catch (JSONException e) { } catch (JSONException e) {
Log.w(LOGTAG, "Exception populating settings items.", 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) { // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
// Do nothing // 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() { builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
ListView listView = ((AlertDialog) dialog).getListView(); 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 // An array of the indices of the permissions we want to clear
JSONArray permissionsToClear = new JSONArray(); JSONArray permissionsToClear = new JSONArray();
for (int i = 0; i < checkedItemPositions.size(); i++) { for (int i = 0; i < checkedItemPositions.size(); i++)
boolean checked = checkedItemPositions.get(i); if (checkedItemPositions.get(i))
if (checked)
permissionsToClear.put(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(){ builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
dialog.cancel(); dialog.cancel();
} }
}); });
mMainHandler.post(new Runnable() { mMainHandler.post(new Runnable() {
public void run() { 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 \ CameraImageResultHandler.java \
CameraVideoResultHandler.java \ CameraVideoResultHandler.java \
CanvasDelegate.java \ CanvasDelegate.java \
CheckableLinearLayout.java \
SyncPreference.java \ SyncPreference.java \
db/BrowserDB.java \ db/BrowserDB.java \
db/LocalBrowserDB.java \ db/LocalBrowserDB.java \
@ -364,6 +365,7 @@ RES_LAYOUT = \
res/layout/notification_icon_text.xml \ res/layout/notification_icon_text.xml \
res/layout/notification_progress.xml \ res/layout/notification_progress.xml \
res/layout/notification_progress_text.xml \ res/layout/notification_progress_text.xml \
res/layout/site_setting_item.xml \
res/layout/site_setting_title.xml \ res/layout/site_setting_title.xml \
res/layout/setup_screen.xml \ res/layout/setup_screen.xml \
res/layout/shared_ui_components.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"; "allowed" : "denied";
let valueString = Strings.browser.GetStringFromName(typeStrings[valueKey]); 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({ permissions.push({
type: type, type: type,
setting: setting setting: label,
value: valueString
}); });
} }
// Keep track of permissions, so we know which ones to clear // Keep track of permissions, so we know which ones to clear
this._currentPermissions = permissions; this._currentPermissions = permissions;
let host; let host;
try { try {

View File

@ -7,6 +7,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <prlong.h>
#include <prprf.h>
#include <prtime.h>
#include "nsProfileLock.h" #include "nsProfileLock.h"
#ifdef XP_WIN #ifdef XP_WIN
@ -122,6 +125,8 @@ private:
NS_HIDDEN_(nsresult) Init(); NS_HIDDEN_(nsresult) Init();
nsresult CreateTimesInternal(nsIFile *profileDir);
nsresult CreateProfileInternal(nsIFile* aRootDir, nsresult CreateProfileInternal(nsIFile* aRootDir,
nsIFile* aLocalDir, nsIFile* aLocalDir,
const nsACString& aName, const nsACString& aName,
@ -813,6 +818,12 @@ nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
NS_ENSURE_SUCCESS(rv, rv); 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; nsToolkitProfile* last = aForExternalApp ? nullptr : mFirst;
if (last) { if (last) {
while (last->mNext) while (last->mNext)
@ -827,6 +838,40 @@ nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
return NS_OK; 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 NS_IMETHODIMP
nsToolkitProfileService::GetProfileCount(uint32_t *aResult) nsToolkitProfileService::GetProfileCount(uint32_t *aResult)
{ {

View File

@ -29,8 +29,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=543854
const ASCIIName = "myprofile"; const ASCIIName = "myprofile";
const UnicodeName = "\u09A0\u09BE\u0995\u09C1\u09B0"; // A Bengali name const UnicodeName = "\u09A0\u09BE\u0995\u09C1\u09B0"; // A Bengali name
var gIOService;
var gProfileService; var gProfileService;
gIOService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
gProfileService = Cc["@mozilla.org/toolkit/profile-service;1"]. gProfileService = Cc["@mozilla.org/toolkit/profile-service;1"].
getService(Ci.nsIToolkitProfileService); getService(Ci.nsIToolkitProfileService);
@ -38,24 +42,72 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=543854
createProfile(UnicodeName); createProfile(UnicodeName);
SimpleTest.finish(); SimpleTest.finish();
function createProfile(profileName) /**
{ * Read the contents of an nsIFile. Throws on error.
var profile = gProfileService.createProfile(null, null, profileName);
* @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 // check that the directory was created
isnot(profile, null, "Profile " + profileName + " created"); isnot(profile, null, "Profile " + profileName + " created");
var profileDir = profile.rootDir; let profileDir = profile.rootDir;
ok(profileDir.exists(), "Profile dir created"); ok(profileDir.exists(), "Profile dir created");
ok(profileDir.isDirectory(), "Profile dir is a directory"); ok(profileDir.isDirectory(), "Profile dir is a directory");
var profileDirPath = profileDir.path; let profileDirPath = profileDir.path;
is(profileDirPath.substr(profileDirPath.length - profileName.length), is(profileDirPath.substr(profileDirPath.length - profileName.length),
profileName, "Profile dir has expected name"); 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); profile.remove(true);
} }