Revision: 13645
Author: [email protected]
Date: Tue Feb 12 03:44:08 2013
Log: Separated smi check from HBoundsCheck.
Review URL: https://codereview.chromium.org/12208013
http://code.google.com/p/v8/source/detail?r=13645
Modified:
/branches/bleeding_edge/src/arm/lithium-arm.cc
/branches/bleeding_edge/src/arm/lithium-codegen-arm.cc
/branches/bleeding_edge/src/arm/lithium-codegen-arm.h
/branches/bleeding_edge/src/code-stubs-hydrogen.cc
/branches/bleeding_edge/src/hydrogen-instructions.cc
/branches/bleeding_edge/src/hydrogen-instructions.h
/branches/bleeding_edge/src/hydrogen.cc
/branches/bleeding_edge/src/hydrogen.h
/branches/bleeding_edge/src/ia32/lithium-codegen-ia32.cc
/branches/bleeding_edge/src/ia32/lithium-codegen-ia32.h
/branches/bleeding_edge/src/ia32/lithium-ia32.cc
/branches/bleeding_edge/src/mips/lithium-codegen-mips.cc
/branches/bleeding_edge/src/mips/lithium-codegen-mips.h
/branches/bleeding_edge/src/mips/lithium-mips.cc
/branches/bleeding_edge/src/x64/lithium-codegen-x64.cc
/branches/bleeding_edge/src/x64/lithium-codegen-x64.h
/branches/bleeding_edge/src/x64/lithium-x64.cc
=======================================
--- /branches/bleeding_edge/src/arm/lithium-arm.cc Tue Feb 5 00:09:32 2013
+++ /branches/bleeding_edge/src/arm/lithium-arm.cc Tue Feb 12 03:44:08 2013
@@ -1822,6 +1822,12 @@
LOperand* value = UseRegisterAtStart(instr->value());
return AssignEnvironment(new(zone()) LCheckSmi(value));
}
+
+
+LInstruction* LChunkBuilder::DoCheckSmiOrInt32(HCheckSmiOrInt32* instr) {
+ LOperand* value = UseRegisterAtStart(instr->value());
+ return AssignEnvironment(new(zone()) LCheckSmi(value));
+}
LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
=======================================
--- /branches/bleeding_edge/src/arm/lithium-codegen-arm.cc Fri Feb 8
09:32:47 2013
+++ /branches/bleeding_edge/src/arm/lithium-codegen-arm.cc Tue Feb 12
03:44:08 2013
@@ -4313,30 +4313,11 @@
: isolate()->builtins()->StoreIC_Initialize();
CallCode(ic, RelocInfo::CODE_TARGET, instr, NEVER_INLINE_TARGET_ADDRESS);
}
-
-
-void LCodeGen::DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand) {
- if (value->representation().IsTagged() && !value->type().IsSmi()) {
- if (operand->IsRegister()) {
- __ tst(ToRegister(operand), Operand(kSmiTagMask));
- } else {
- __ mov(ip, ToOperand(operand));
- __ tst(ip, Operand(kSmiTagMask));
- }
- DeoptimizeIf(ne, environment);
- }
-}
void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) {
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->length(),
- instr->length());
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->index(),
- instr->index());
+ if (instr->hydrogen()->skip_check()) return;
+
if (instr->index()->IsConstantOperand()) {
int constant_index =
ToInteger32(LConstantOperand::cast(instr->index()));
=======================================
--- /branches/bleeding_edge/src/arm/lithium-codegen-arm.h Mon Feb 4
04:01:59 2013
+++ /branches/bleeding_edge/src/arm/lithium-codegen-arm.h Tue Feb 12
03:44:08 2013
@@ -326,10 +326,6 @@
LEnvironment* env,
NumberUntagDMode mode);
- void DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand);
-
// Emits optimized code for typeof x == "y". Modifies input register.
// Returns the condition on which a final split to
// true and false label should be made, to optimize fallthrough.
=======================================
--- /branches/bleeding_edge/src/code-stubs-hydrogen.cc Fri Feb 8 03:56:15
2013
+++ /branches/bleeding_edge/src/code-stubs-hydrogen.cc Tue Feb 12 03:44:08
2013
@@ -178,7 +178,11 @@
HConstant* max_alloc_size =
new(zone) HConstant(kMinFreeNewSpaceAfterGC,
Representation::Integer32());
AddInstruction(max_alloc_size);
- AddInstruction(new(zone) HBoundsCheck(array_length, max_alloc_size));
+ // Since we're forcing Integer32 representation for this HBoundsCheck,
+ // there's no need to Smi-check the index.
+ AddInstruction(
+ new(zone) HBoundsCheck(array_length, max_alloc_size,
+ DONT_ALLOW_SMI_KEY,
Representation::Integer32()));
current_block()->UpdateEnvironment(new(zone) HEnvironment(zone));
=======================================
--- /branches/bleeding_edge/src/hydrogen-instructions.cc Mon Feb 4
04:01:59 2013
+++ /branches/bleeding_edge/src/hydrogen-instructions.cc Tue Feb 12
03:44:08 2013
@@ -829,9 +829,8 @@
!length()->representation().IsTagged()) {
r = Representation::Integer32();
} else if (index()->representation().IsTagged() ||
- (index()->IsConstant() &&
- HConstant::cast(index())->HasInteger32Value() &&
- Smi::IsValid(HConstant::cast(index())->Integer32Value()))) {
+ (index()->ActualValue()->IsConstant() &&
+ HConstant::cast(index()->ActualValue())->HasSmiValue())) {
// If the index is tagged, or a constant that holds a Smi, allow the
length
// to be tagged, since it is usually already tagged from loading it
out of
// the length field of a JSArray. This allows for direct comparison
without
@@ -2390,6 +2389,14 @@
HType HCheckSmi::CalculateInferredType() {
return HType::Smi();
}
+
+
+void HCheckSmiOrInt32::InferRepresentation(HInferRepresentation* h_infer) {
+ ASSERT(CheckFlag(kFlexibleRepresentation));
+ Representation r = value()->representation().IsTagged()
+ ? Representation::Tagged() : Representation::Integer32();
+ UpdateRepresentation(r, h_infer, "checksmiorint32");
+}
HType HPhi::CalculateInferredType() {
=======================================
--- /branches/bleeding_edge/src/hydrogen-instructions.h Tue Feb 5 00:09:32
2013
+++ /branches/bleeding_edge/src/hydrogen-instructions.h Tue Feb 12 03:44:08
2013
@@ -92,6 +92,7 @@
V(CheckNonSmi) \
V(CheckPrototypeMaps) \
V(CheckSmi) \
+ V(CheckSmiOrInt32) \
V(ClampToUint8) \
V(ClassOfTestAndBranch) \
V(CompareIDAndBranch) \
@@ -2611,6 +2612,34 @@
virtual bool DataEquals(HValue* other) { return true; }
};
+
+class HCheckSmiOrInt32: public HUnaryOperation {
+ public:
+ explicit HCheckSmiOrInt32(HValue* value) : HUnaryOperation(value) {
+ SetFlag(kFlexibleRepresentation);
+ SetFlag(kUseGVN);
+ }
+
+ virtual int RedefinedOperandIndex() { return 0; }
+ virtual Representation RequiredInputRepresentation(int index) {
+ return representation();
+ }
+ virtual void InferRepresentation(HInferRepresentation* h_infer);
+
+ virtual HValue* Canonicalize() {
+ if (representation().IsTagged() && !type().IsSmi()) {
+ return this;
+ } else {
+ return value();
+ }
+ }
+
+ DECLARE_CONCRETE_INSTRUCTION(CheckSmiOrInt32)
+
+ protected:
+ virtual bool DataEquals(HValue* other) { return true; }
+};
+
class HPhi: public HValue {
public:
@@ -2807,6 +2836,9 @@
ASSERT(HasInteger32Value());
return int32_value_;
}
+ bool HasSmiValue() const {
+ return HasInteger32Value() && Smi::IsValid(Integer32Value());
+ }
bool HasDoubleValue() const { return has_double_value_; }
double DoubleValue() const {
ASSERT(HasDoubleValue());
@@ -3088,15 +3120,21 @@
class HBoundsCheck: public HTemplateInstruction<2> {
public:
- HBoundsCheck(HValue* index, HValue* length,
+ // Normally HBoundsCheck should be created using the
+ // HGraphBuilder::AddBoundsCheck() helper, which also guards the index
with
+ // a HCheckSmiOrInt32 check.
+ // However when building stubs, where we know that the arguments are
Int32,
+ // it makes sense to invoke this constructor directly.
+ HBoundsCheck(HValue* index,
+ HValue* length,
BoundsCheckKeyMode key_mode = DONT_ALLOW_SMI_KEY,
Representation r = Representation::None())
- : key_mode_(key_mode) {
+ : key_mode_(key_mode), skip_check_(false) {
SetOperandAt(0, index);
SetOperandAt(1, length);
if (r.IsNone()) {
// In the normal compilation pipeline the representation is flexible
- // (see comment to RequiredInputRepresentation).
+ // (see InferRepresentation).
SetFlag(kFlexibleRepresentation);
} else {
// When compiling stubs we want to set the representation explicitly
@@ -3105,6 +3143,9 @@
}
SetFlag(kUseGVN);
}
+
+ bool skip_check() { return skip_check_; }
+ void set_skip_check(bool skip_check) { skip_check_ = skip_check; }
virtual Representation RequiredInputRepresentation(int arg_index) {
return representation();
@@ -3126,6 +3167,7 @@
protected:
virtual bool DataEquals(HValue* other) { return true; }
BoundsCheckKeyMode key_mode_;
+ bool skip_check_;
};
=======================================
--- /branches/bleeding_edge/src/hydrogen.cc Mon Feb 4 04:01:59 2013
+++ /branches/bleeding_edge/src/hydrogen.cc Tue Feb 12 03:44:08 2013
@@ -777,6 +777,20 @@
ASSERT(current_block() != NULL);
current_block()->AddSimulate(id, removable);
}
+
+
+HBoundsCheck* HGraphBuilder::AddBoundsCheck(HValue* index,
+ HValue* length,
+ BoundsCheckKeyMode key_mode,
+ Representation r) {
+ HCheckSmiOrInt32* checked_index =
+ new(graph()->zone()) HCheckSmiOrInt32(index);
+ AddInstruction(checked_index);
+ HBoundsCheck* result = new(graph()->zone()) HBoundsCheck(
+ checked_index, length, key_mode, r);
+ AddInstruction(result);
+ return result;
+}
HBasicBlock* HGraphBuilder::CreateBasicBlock(HEnvironment* env) {
@@ -918,8 +932,8 @@
HInstruction* checked_key = NULL;
if (IsExternalArrayElementsKind(elements_kind)) {
length = AddInstruction(new(zone) HFixedArrayBaseLength(elements));
- checked_key = AddInstruction(new(zone) HBoundsCheck(
- key, length, ALLOW_SMI_KEY, checked_index_representation));
+ checked_key = AddBoundsCheck(
+ key, length, ALLOW_SMI_KEY, checked_index_representation);
HLoadExternalArrayPointer* external_elements =
new(zone) HLoadExternalArrayPointer(elements);
AddInstruction(external_elements);
@@ -936,8 +950,8 @@
} else {
length = AddInstruction(new(zone) HFixedArrayBaseLength(elements));
}
- checked_key = AddInstruction(new(zone) HBoundsCheck(
- key, length, ALLOW_SMI_KEY, checked_index_representation));
+ checked_key = AddBoundsCheck(
+ key, length, ALLOW_SMI_KEY, checked_index_representation);
return BuildFastElementAccess(elements, checked_key, val, mapcheck,
elements_kind, is_store);
}
@@ -6833,7 +6847,7 @@
&& todo_external_array) {
HInstruction* length =
AddInstruction(new(zone()) HFixedArrayBaseLength(elements));
- checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length));
+ checked_key = AddBoundsCheck(key, length);
external_elements = new(zone()) HLoadExternalArrayPointer(elements);
AddInstruction(external_elements);
}
@@ -6875,8 +6889,7 @@
HInstruction* length;
length = AddInstruction(new(zone()) HJSArrayLength(object,
typecheck,
HType::Smi()));
- checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length,
-
ALLOW_SMI_KEY));
+ checked_key = AddBoundsCheck(key, length, ALLOW_SMI_KEY);
access = AddInstruction(BuildFastElementAccess(
elements, checked_key, val, elements_kind_branch,
elements_kind, is_store));
@@ -6892,8 +6905,7 @@
set_current_block(if_fastobject);
length = AddInstruction(new(zone())
HFixedArrayBaseLength(elements));
- checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length,
-
ALLOW_SMI_KEY));
+ checked_key = AddBoundsCheck(key, length, ALLOW_SMI_KEY);
access = AddInstruction(BuildFastElementAccess(
elements, checked_key, val, elements_kind_branch,
elements_kind, is_store));
@@ -7042,8 +7054,7 @@
new(zone()) HArgumentsElements(false));
HInstruction* length = AddInstruction(
new(zone()) HArgumentsLength(elements));
- HInstruction* checked_key =
- AddInstruction(new(zone()) HBoundsCheck(key, length));
+ HInstruction* checked_key = AddBoundsCheck(key, length);
result = new(zone()) HAccessArgumentsAt(elements, length,
checked_key);
} else {
EnsureArgumentsArePushedForAccess();
@@ -7055,8 +7066,7 @@
HInstruction* length = AddInstruction(new(zone()) HConstant(
Handle<Object>(Smi::FromInt(argument_count)),
Representation::Integer32()));
- HInstruction* checked_key =
- AddInstruction(new(zone()) HBoundsCheck(key, length));
+ HInstruction* checked_key = AddBoundsCheck(key, length);
result = new(zone()) HAccessArgumentsAt(elements, length,
checked_key);
}
}
@@ -8770,8 +8780,7 @@
AddInstruction(HCheckInstanceType::NewIsString(string, zone()));
HStringLength* length = new(zone()) HStringLength(string);
AddInstruction(length);
- HInstruction* checked_index =
- AddInstruction(new(zone()) HBoundsCheck(index, length));
+ HInstruction* checked_index = AddBoundsCheck(index, length);
return new(zone()) HStringCharCodeAt(context, string, checked_index);
}
@@ -9599,8 +9608,7 @@
HInstruction* elements = AddInstruction(
new(zone()) HArgumentsElements(false));
HInstruction* length = AddInstruction(new(zone())
HArgumentsLength(elements));
- HInstruction* checked_index =
- AddInstruction(new(zone()) HBoundsCheck(index, length));
+ HInstruction* checked_index = AddBoundsCheck(index, length);
HAccessArgumentsAt* result =
new(zone()) HAccessArgumentsAt(elements, length, checked_index);
return ast_context()->ReturnInstruction(result, call->id());
=======================================
--- /branches/bleeding_edge/src/hydrogen.h Mon Feb 4 04:01:59 2013
+++ /branches/bleeding_edge/src/hydrogen.h Tue Feb 12 03:44:08 2013
@@ -875,6 +875,11 @@
HInstruction* AddInstruction(HInstruction* instr);
void AddSimulate(BailoutId id,
RemovableSimulate removable = FIXED_SIMULATE);
+ HBoundsCheck* AddBoundsCheck(
+ HValue* index,
+ HValue* length,
+ BoundsCheckKeyMode key_mode = DONT_ALLOW_SMI_KEY,
+ Representation r = Representation::None());
protected:
virtual bool BuildGraph() = 0;
=======================================
--- /branches/bleeding_edge/src/ia32/lithium-codegen-ia32.cc Mon Feb 11
06:44:25 2013
+++ /branches/bleeding_edge/src/ia32/lithium-codegen-ia32.cc Tue Feb 12
03:44:08 2013
@@ -4149,27 +4149,9 @@
}
-void LCodeGen::DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand) {
- if (value->representation().IsTagged() && !value->type().IsSmi()) {
- if (operand->IsRegister()) {
- __ test(ToRegister(operand), Immediate(kSmiTagMask));
- } else {
- __ test(ToOperand(operand), Immediate(kSmiTagMask));
- }
- DeoptimizeIf(not_zero, environment);
- }
-}
-
+void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) {
+ if (instr->hydrogen()->skip_check()) return;
-void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) {
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->length(),
- instr->length());
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->index(),
- instr->index());
if (instr->index()->IsConstantOperand()) {
int constant_index =
ToInteger32(LConstantOperand::cast(instr->index()));
=======================================
--- /branches/bleeding_edge/src/ia32/lithium-codegen-ia32.h Mon Feb 4
04:01:59 2013
+++ /branches/bleeding_edge/src/ia32/lithium-codegen-ia32.h Tue Feb 12
03:44:08 2013
@@ -314,10 +314,6 @@
LEnvironment* env,
NumberUntagDMode mode = NUMBER_CANDIDATE_IS_ANY_TAGGED);
- void DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand);
-
// Emits optimized code for typeof x == "y". Modifies input register.
// Returns the condition on which a final split to
// true and false label should be made, to optimize fallthrough.
=======================================
--- /branches/bleeding_edge/src/ia32/lithium-ia32.cc Tue Feb 5 00:09:32
2013
+++ /branches/bleeding_edge/src/ia32/lithium-ia32.cc Tue Feb 12 03:44:08
2013
@@ -1855,6 +1855,12 @@
LOperand* value = UseAtStart(instr->value());
return AssignEnvironment(new(zone()) LCheckSmi(value));
}
+
+
+LInstruction* LChunkBuilder::DoCheckSmiOrInt32(HCheckSmiOrInt32* instr) {
+ LOperand* value = UseAtStart(instr->value());
+ return AssignEnvironment(new(zone()) LCheckSmi(value));
+}
LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
=======================================
--- /branches/bleeding_edge/src/mips/lithium-codegen-mips.cc Tue Feb 12
01:34:40 2013
+++ /branches/bleeding_edge/src/mips/lithium-codegen-mips.cc Tue Feb 12
03:44:08 2013
@@ -3948,31 +3948,11 @@
: isolate()->builtins()->StoreIC_Initialize();
CallCode(ic, RelocInfo::CODE_TARGET, instr);
}
-
-
-void LCodeGen::DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand) {
- if (value->representation().IsTagged() && !value->type().IsSmi()) {
- if (operand->IsRegister()) {
- __ And(at, ToRegister(operand), Operand(kSmiTagMask));
- DeoptimizeIf(ne, environment, at, Operand(zero_reg));
- } else {
- __ li(at, ToOperand(operand));
- __ And(at, at, Operand(kSmiTagMask));
- DeoptimizeIf(ne, environment, at, Operand(zero_reg));
- }
- }
-}
void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) {
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->length(),
- instr->length());
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->index(),
- instr->index());
+ if (instr->hydrogen()->skip_check()) return;
+
if (instr->index()->IsConstantOperand()) {
int constant_index =
ToInteger32(LConstantOperand::cast(instr->index()));
=======================================
--- /branches/bleeding_edge/src/mips/lithium-codegen-mips.h Mon Jan 21
00:30:11 2013
+++ /branches/bleeding_edge/src/mips/lithium-codegen-mips.h Tue Feb 12
03:44:08 2013
@@ -328,10 +328,6 @@
bool deoptimize_on_minus_zero,
LEnvironment* env);
- void DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand);
-
// Emits optimized code for typeof x == "y". Modifies input register.
// Returns the condition on which a final split to
// true and false label should be made, to optimize fallthrough.
=======================================
--- /branches/bleeding_edge/src/mips/lithium-mips.cc Wed Jan 23 07:25:28
2013
+++ /branches/bleeding_edge/src/mips/lithium-mips.cc Tue Feb 12 03:44:08
2013
@@ -1727,6 +1727,12 @@
LOperand* value = UseRegisterAtStart(instr->value());
return AssignEnvironment(new(zone()) LCheckSmi(value));
}
+
+
+LInstruction* LChunkBuilder::DoCheckSmiOrInt32(HCheckSmiOrInt32* instr) {
+ LOperand* value = UseRegisterAtStart(instr->value());
+ return AssignEnvironment(new(zone()) LCheckSmi(value));
+}
LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
=======================================
--- /branches/bleeding_edge/src/x64/lithium-codegen-x64.cc Fri Feb 8
09:32:47 2013
+++ /branches/bleeding_edge/src/x64/lithium-codegen-x64.cc Tue Feb 12
03:44:08 2013
@@ -3929,30 +3929,11 @@
: isolate()->builtins()->StoreIC_Initialize();
CallCode(ic, RelocInfo::CODE_TARGET, instr);
}
-
-
-void LCodeGen::DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand) {
- if (value->representation().IsTagged() && !value->type().IsSmi()) {
- Condition cc;
- if (operand->IsRegister()) {
- cc = masm()->CheckSmi(ToRegister(operand));
- } else {
- cc = masm()->CheckSmi(ToOperand(operand));
- }
- DeoptimizeIf(NegateCondition(cc), environment);
- }
-}
void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) {
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->length(),
- instr->length());
- DeoptIfTaggedButNotSmi(instr->environment(),
- instr->hydrogen()->index(),
- instr->index());
+ if (instr->hydrogen()->skip_check()) return;
+
if (instr->length()->IsRegister()) {
Register reg = ToRegister(instr->length());
if (!instr->hydrogen()->length()->representation().IsTagged()) {
=======================================
--- /branches/bleeding_edge/src/x64/lithium-codegen-x64.h Mon Feb 4
04:01:59 2013
+++ /branches/bleeding_edge/src/x64/lithium-codegen-x64.h Tue Feb 12
03:44:08 2013
@@ -291,11 +291,6 @@
LEnvironment* env,
NumberUntagDMode mode = NUMBER_CANDIDATE_IS_ANY_TAGGED);
-
- void DeoptIfTaggedButNotSmi(LEnvironment* environment,
- HValue* value,
- LOperand* operand);
-
// Emits optimized code for typeof x == "y". Modifies input register.
// Returns the condition on which a final split to
// true and false label should be made, to optimize fallthrough.
=======================================
--- /branches/bleeding_edge/src/x64/lithium-x64.cc Tue Feb 5 00:09:32 2013
+++ /branches/bleeding_edge/src/x64/lithium-x64.cc Tue Feb 12 03:44:08 2013
@@ -1764,6 +1764,12 @@
LOperand* value = UseRegisterAtStart(instr->value());
return AssignEnvironment(new(zone()) LCheckSmi(value));
}
+
+
+LInstruction* LChunkBuilder::DoCheckSmiOrInt32(HCheckSmiOrInt32* instr) {
+ LOperand* value = UseRegisterAtStart(instr->value());
+ return AssignEnvironment(new(zone()) LCheckSmi(value));
+}
LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.