Improvements for x87 stack handling

BUG=

Review URL: https://codereview.chromium.org/13426006

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14179 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 74839e86
......@@ -3190,13 +3190,19 @@ class HConstant: public HTemplateInstruction<0> {
bool InOldSpace() const { return !HEAP->InNewSpace(*handle_); }
bool IsSpecialDouble() const {
return has_double_value_ &&
(BitCast<int64_t>(double_value_) == BitCast<int64_t>(-0.0) ||
FixedDoubleArray::is_the_hole_nan(double_value_) ||
isnan(double_value_));
}
bool ImmortalImmovable() const {
if (has_int32_value_) {
return false;
}
if (has_double_value_) {
if (BitCast<int64_t>(double_value_) == BitCast<int64_t>(-0.0) ||
isnan(double_value_)) {
if (IsSpecialDouble()) {
return true;
}
return false;
......@@ -3227,7 +3233,9 @@ class HConstant: public HTemplateInstruction<0> {
return has_int32_value_;
}
virtual bool EmitAtUses() { return !representation().IsDouble(); }
virtual bool EmitAtUses() {
return !representation().IsDouble() || IsSpecialDouble();
}
virtual void PrintDataTo(StringStream* stream);
virtual HType CalculateInferredType();
bool IsInteger() { return handle()->IsSmi(); }
......@@ -3246,6 +3254,16 @@ class HConstant: public HTemplateInstruction<0> {
ASSERT(HasDoubleValue());
return double_value_;
}
bool IsTheHole() const {
if (HasDoubleValue() && FixedDoubleArray::is_the_hole_nan(double_value_)) {
return true;
}
Heap* heap = isolate()->heap();
if (!handle_.is_null() && *handle_ == heap->the_hole_value()) {
return true;
}
return false;
}
bool HasNumberValue() const { return has_double_value_; }
int32_t NumberValueAsInteger32() const {
ASSERT(HasNumberValue());
......@@ -5677,6 +5695,10 @@ class HStoreKeyed
bool IsDehoisted() { return is_dehoisted_; }
void SetDehoisted(bool is_dehoisted) { is_dehoisted_ = is_dehoisted; }
bool IsConstantHoleStore() {
return value()->IsConstant() && HConstant::cast(value())->IsTheHole();
}
virtual void SetSideEffectDominator(GVNFlag side_effect, HValue* dominator) {
ASSERT(side_effect == kChangesNewSpacePromotion);
new_space_dominator_ = dominator;
......
This diff is collapsed.
......@@ -68,6 +68,7 @@ class LCodeGen BASE_EMBEDDED {
osr_pc_offset_(-1),
last_lazy_deopt_pc_(0),
frame_is_built_(false),
x87_stack_depth_(0),
safepoints_(info->zone()),
resolver_(this),
expected_safepoint_kind_(Safepoint::kSimple) {
......@@ -102,10 +103,17 @@ class LCodeGen BASE_EMBEDDED {
return Immediate(ToInteger32(LConstantOperand::cast(op)));
}
Handle<Object> ToHandle(LConstantOperand* op) const;
// Support for non-sse2 (x87) floating point stack handling.
// These functions maintain the depth of the stack (either 0 or 1)
void PushX87DoubleOperand(Operand src);
void PushX87FloatOperand(Operand src);
void ReadX87Operand(Operand dst);
bool X87StackNonEmpty() const { return x87_stack_depth_ > 0; }
void PopX87();
void CurrentInstructionReturnsX87Result();
void FlushX87StackIfNecessary(LInstruction* instr);
// A utility for instructions that return floating point values on X87.
void HandleX87FPReturnValue(LInstruction* instr);
Handle<Object> ToHandle(LConstantOperand* op) const;
// The operand denoting the second word (the one with a higher address) of
// a double stack slot.
......@@ -129,6 +137,7 @@ class LCodeGen BASE_EMBEDDED {
IntegerSignedness signedness);
void DoDeferredTaggedToI(LTaggedToI* instr);
void DoDeferredTaggedToINoSSE2(LTaggedToINoSSE2* instr);
void DoDeferredMathAbsTaggedHeapNumber(LUnaryMathOperation* instr);
void DoDeferredStackCheck(LStackCheck* instr);
void DoDeferredRandom(LRandom* instr);
......@@ -315,6 +324,14 @@ class LCodeGen BASE_EMBEDDED {
LEnvironment* env,
NumberUntagDMode mode = NUMBER_CANDIDATE_IS_ANY_TAGGED);
void EmitNumberUntagDNoSSE2(
Register input,
Register temp,
bool deoptimize_on_undefined,
bool deoptimize_on_minus_zero,
LEnvironment* env,
NumberUntagDMode mode = NUMBER_CANDIDATE_IS_ANY_TAGGED);
// 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.
......@@ -404,6 +421,7 @@ class LCodeGen BASE_EMBEDDED {
int osr_pc_offset_;
int last_lazy_deopt_pc_;
bool frame_is_built_;
int x87_stack_depth_;
// Builder that keeps track of safepoints in the code. The table
// itself is emitted at the end of the generated code.
......
......@@ -324,29 +324,61 @@ void LGapResolver::EmitMove(int index) {
}
} else if (source->IsDoubleRegister()) {
CpuFeatureScope scope(cgen_->masm(), SSE2);
XMMRegister src = cgen_->ToDoubleRegister(source);
if (destination->IsDoubleRegister()) {
XMMRegister dst = cgen_->ToDoubleRegister(destination);
__ movaps(dst, src);
if (CpuFeatures::IsSupported(SSE2)) {
CpuFeatureScope scope(cgen_->masm(), SSE2);
XMMRegister src = cgen_->ToDoubleRegister(source);
if (destination->IsDoubleRegister()) {
XMMRegister dst = cgen_->ToDoubleRegister(destination);
__ movaps(dst, src);
} else {
ASSERT(destination->IsDoubleStackSlot());
Operand dst = cgen_->ToOperand(destination);
__ movdbl(dst, src);
}
} else {
// load from the register onto the stack, store in destination, which must
// be a double stack slot in the non-SSE2 case.
ASSERT(source->index() == 0); // source is on top of the stack
ASSERT(destination->IsDoubleStackSlot());
Operand dst = cgen_->ToOperand(destination);
__ movdbl(dst, src);
cgen_->ReadX87Operand(dst);
}
} else if (source->IsDoubleStackSlot()) {
CpuFeatureScope scope(cgen_->masm(), SSE2);
ASSERT(destination->IsDoubleRegister() ||
destination->IsDoubleStackSlot());
Operand src = cgen_->ToOperand(source);
if (destination->IsDoubleRegister()) {
XMMRegister dst = cgen_->ToDoubleRegister(destination);
__ movdbl(dst, src);
if (CpuFeatures::IsSupported(SSE2)) {
CpuFeatureScope scope(cgen_->masm(), SSE2);
ASSERT(destination->IsDoubleRegister() ||
destination->IsDoubleStackSlot());
Operand src = cgen_->ToOperand(source);
if (destination->IsDoubleRegister()) {
XMMRegister dst = cgen_->ToDoubleRegister(destination);
__ movdbl(dst, src);
} else {
// We rely on having xmm0 available as a fixed scratch register.
Operand dst = cgen_->ToOperand(destination);
__ movdbl(xmm0, src);
__ movdbl(dst, xmm0);
}
} else {
// We rely on having xmm0 available as a fixed scratch register.
Operand dst = cgen_->ToOperand(destination);
__ movdbl(xmm0, src);
__ movdbl(dst, xmm0);
// load from the stack slot on top of the floating point stack, and then
// store in destination. If destination is a double register, then it
// represents the top of the stack and nothing needs to be done.
if (destination->IsDoubleStackSlot()) {
Register tmp = EnsureTempRegister();
Operand src0 = cgen_->ToOperand(source);
Operand src1 = cgen_->HighOperand(source);
Operand dst0 = cgen_->ToOperand(destination);
Operand dst1 = cgen_->HighOperand(destination);
__ mov(tmp, src0); // Then use tmp to copy source to destination.
__ mov(dst0, tmp);
__ mov(tmp, src1);
__ mov(dst1, tmp);
} else {
Operand src = cgen_->ToOperand(source);
if (cgen_->X87StackNonEmpty()) {
cgen_->PopX87();
}
cgen_->PushX87DoubleOperand(src);
}
}
} else {
UNREACHABLE();
......@@ -419,21 +451,19 @@ void LGapResolver::EmitSwap(int index) {
__ movaps(xmm0, src);
__ movaps(src, dst);
__ movaps(dst, xmm0);
} else if (source->IsDoubleRegister() || destination->IsDoubleRegister()) {
CpuFeatureScope scope(cgen_->masm(), SSE2);
// XMM register-memory swap. We rely on having xmm0
// available as a fixed scratch register.
ASSERT(source->IsDoubleStackSlot() || destination->IsDoubleStackSlot());
XMMRegister reg = cgen_->ToDoubleRegister(source->IsDoubleRegister()
? source
: destination);
? source
: destination);
Operand other =
cgen_->ToOperand(source->IsDoubleRegister() ? destination : source);
__ movdbl(xmm0, other);
__ movdbl(other, reg);
__ movdbl(reg, Operand(xmm0));
} else if (source->IsDoubleStackSlot() && destination->IsDoubleStackSlot()) {
CpuFeatureScope scope(cgen_->masm(), SSE2);
// Double-width memory-to-memory. Spill on demand to use a general
......
......@@ -91,6 +91,22 @@ void LInstruction::VerifyCall() {
#endif
bool LInstruction::HasDoubleRegisterResult() {
return HasResult() && result()->IsDoubleRegister();
}
bool LInstruction::HasDoubleRegisterInput() {
for (int i = 0; i < InputCount(); i++) {
LOperand* op = InputAt(i);
if (op->IsDoubleRegister()) {
return true;
}
}
return false;
}
void LInstruction::PrintTo(StringStream* stream) {
stream->Add("%s ", this->Mnemonic());
......@@ -542,6 +558,11 @@ LOperand* LChunkBuilder::UseFixedDouble(HValue* value, XMMRegister reg) {
}
LOperand* LChunkBuilder::UseX87TopOfStack(HValue* value) {
return Use(value, ToUnallocated(x87tos));
}
LOperand* LChunkBuilder::UseRegister(HValue* value) {
return Use(value, new(zone()) LUnallocated(LUnallocated::MUST_HAVE_REGISTER));
}
......@@ -1861,20 +1882,33 @@ LInstruction* LChunkBuilder::DoChange(HChange* instr) {
? TempRegister()
: NULL;
LNumberUntagD* res = new(zone()) LNumberUntagD(value, temp);
return AssignEnvironment(DefineAsRegister(res));
if (CpuFeatures::IsSafeForSnapshot(SSE2)) {
return AssignEnvironment(DefineAsRegister(res));
} else {
return AssignEnvironment(DefineX87TOS(res));
}
} else {
ASSERT(to.IsInteger32());
LOperand* value = UseRegister(instr->value());
if (instr->value()->type().IsSmi()) {
LOperand* value = UseRegister(instr->value());
return DefineSameAsFirst(new(zone()) LSmiUntag(value, false));
} else {
bool truncating = instr->CanTruncateToInt32();
LOperand* xmm_temp =
(truncating && CpuFeatures::IsSupported(SSE3))
? NULL
: FixedTemp(xmm1);
LTaggedToI* res = new(zone()) LTaggedToI(value, xmm_temp);
return AssignEnvironment(DefineSameAsFirst(res));
if (CpuFeatures::IsSafeForSnapshot(SSE2)) {
LOperand* value = UseRegister(instr->value());
LOperand* xmm_temp =
(truncating && CpuFeatures::IsSupported(SSE3))
? NULL
: FixedTemp(xmm1);
LTaggedToI* res = new(zone()) LTaggedToI(value, xmm_temp);
return AssignEnvironment(DefineSameAsFirst(res));
} else {
LOperand* value = UseFixed(instr->value(), ecx);
LTaggedToINoSSE2* res =
new(zone()) LTaggedToINoSSE2(value, TempRegister(),
TempRegister(), TempRegister());
return AssignEnvironment(DefineFixed(res, ecx));
}
}
}
} else if (from.IsDouble()) {
......@@ -1992,12 +2026,20 @@ LInstruction* LChunkBuilder::DoClampToUint8(HClampToUint8* instr) {
return DefineFixed(new(zone()) LClampIToUint8(reg), eax);
} else {
ASSERT(input_rep.IsTagged());
LOperand* reg = UseFixed(value, eax);
// Register allocator doesn't (yet) support allocation of double
// temps. Reserve xmm1 explicitly.
LOperand* temp = FixedTemp(xmm1);
LClampTToUint8* result = new(zone()) LClampTToUint8(reg, temp);
return AssignEnvironment(DefineFixed(result, eax));
if (CpuFeatures::IsSupported(SSE2)) {
LOperand* reg = UseFixed(value, eax);
// Register allocator doesn't (yet) support allocation of double
// temps. Reserve xmm1 explicitly.
LOperand* temp = FixedTemp(xmm1);
LClampTToUint8* result = new(zone()) LClampTToUint8(reg, temp);
return AssignEnvironment(DefineFixed(result, eax));
} else {
LOperand* value = UseRegister(instr->value());
LClampTToUint8NoSSE2* res =
new(zone()) LClampTToUint8NoSSE2(value, TempRegister(),
TempRegister(), TempRegister());
return AssignEnvironment(DefineFixed(res, ecx));
}
}
}
......@@ -2018,10 +2060,13 @@ LInstruction* LChunkBuilder::DoConstant(HConstant* instr) {
return DefineAsRegister(new(zone()) LConstantI);
} else if (r.IsDouble()) {
double value = instr->DoubleValue();
LOperand* temp = (BitCast<uint64_t, double>(value) != 0)
? TempRegister()
: NULL;
return DefineAsRegister(new(zone()) LConstantD(temp));
bool value_is_zero = BitCast<uint64_t, double>(value) == 0;
if (CpuFeatures::IsSafeForSnapshot(SSE2)) {
LOperand* temp = value_is_zero ? NULL : TempRegister();
return DefineAsRegister(new(zone()) LConstantD(temp));
} else {
return DefineX87TOS(new(zone()) LConstantD(NULL));
}
} else if (r.IsTagged()) {
return DefineAsRegister(new(zone()) LConstantT);
} else {
......@@ -2190,6 +2235,27 @@ LInstruction* LChunkBuilder::DoLoadKeyedGeneric(HLoadKeyedGeneric* instr) {
}
LOperand* LChunkBuilder::GetStoreKeyedValueOperand(HStoreKeyed* instr) {
ElementsKind elements_kind = instr->elements_kind();
// Determine if we need a byte register in this case for the value.
bool val_is_fixed_register =
elements_kind == EXTERNAL_BYTE_ELEMENTS ||
elements_kind == EXTERNAL_UNSIGNED_BYTE_ELEMENTS ||
elements_kind == EXTERNAL_PIXEL_ELEMENTS;
if (val_is_fixed_register) {
return UseFixed(instr->value(), eax);
}
if (!CpuFeatures::IsSafeForSnapshot(SSE2) &&
IsDoubleOrFloatElementsKind(elements_kind)) {
return UseRegisterAtStart(instr->value());
}
return UseRegister(instr->value());
}
LInstruction* LChunkBuilder::DoStoreKeyed(HStoreKeyed* instr) {
if (!instr->is_external()) {
ASSERT(instr->elements()->representation().IsTagged());
......@@ -2198,7 +2264,12 @@ LInstruction* LChunkBuilder::DoStoreKeyed(HStoreKeyed* instr) {
if (instr->value()->representation().IsDouble()) {
LOperand* object = UseRegisterAtStart(instr->elements());
LOperand* val = UseTempRegister(instr->value());
LOperand* val = NULL;
if (CpuFeatures::IsSafeForSnapshot(SSE2)) {
val = UseRegisterAtStart(instr->value());
} else if (!instr->IsConstantHoleStore()) {
val = UseX87TopOfStack(instr->value());
}
LOperand* key = UseRegisterOrConstantAtStart(instr->key());
return new(zone()) LStoreKeyed(object, key, val);
......@@ -2228,15 +2299,7 @@ LInstruction* LChunkBuilder::DoStoreKeyed(HStoreKeyed* instr) {
ASSERT(instr->elements()->representation().IsExternal());
LOperand* external_pointer = UseRegister(instr->elements());
// Determine if we need a byte register in this case for the value.
bool val_is_fixed_register =
elements_kind == EXTERNAL_BYTE_ELEMENTS ||
elements_kind == EXTERNAL_UNSIGNED_BYTE_ELEMENTS ||
elements_kind == EXTERNAL_PIXEL_ELEMENTS;
LOperand* val = val_is_fixed_register
? UseFixed(instr->value(), eax)
: UseRegister(instr->value());
LOperand* val = GetStoreKeyedValueOperand(instr);
bool clobbers_key = ExternalArrayOpRequiresTemp(
instr->key()->representation(), elements_kind);
LOperand* key = clobbers_key
......
......@@ -74,6 +74,7 @@ class LCodeGen;
V(ClampDToUint8) \
V(ClampIToUint8) \
V(ClampTToUint8) \
V(ClampTToUint8NoSSE2) \
V(ClassOfTestAndBranch) \
V(CmpIDAndBranch) \
V(CmpObjectEqAndBranch) \
......@@ -167,6 +168,7 @@ class LCodeGen;
V(StringLength) \
V(SubI) \
V(TaggedToI) \
V(TaggedToINoSSE2) \
V(ThisFunction) \
V(Throw) \
V(ToFastProperties) \
......@@ -265,6 +267,9 @@ class LInstruction: public ZoneObject {
virtual bool HasResult() const = 0;
virtual LOperand* result() = 0;
bool HasDoubleRegisterResult();
bool HasDoubleRegisterInput();
LOperand* FirstInput() { return InputAt(0); }
LOperand* Output() { return HasResult() ? result() : NULL; }
......@@ -1088,6 +1093,10 @@ class LConstantD: public LTemplateInstruction<1, 0, 1> {
temps_[0] = temp;
}
virtual bool ClobbersDoubleRegisters() const {
return false;
}
LOperand* temp() { return temps_[0]; }
DECLARE_CONCRETE_INSTRUCTION(ConstantD, "constant-d")
......@@ -2018,6 +2027,31 @@ class LTaggedToI: public LTemplateInstruction<1, 1, 1> {
};
// Truncating conversion from a tagged value to an int32.
class LTaggedToINoSSE2: public LTemplateInstruction<1, 1, 3> {
public:
LTaggedToINoSSE2(LOperand* value,
LOperand* temp1,
LOperand* temp2,
LOperand* temp3) {
inputs_[0] = value;
temps_[0] = temp1;
temps_[1] = temp2;
temps_[2] = temp3;
}
LOperand* value() { return inputs_[0]; }
LOperand* scratch() { return temps_[0]; }
LOperand* scratch2() { return temps_[1]; }
LOperand* scratch3() { return temps_[2]; }
DECLARE_CONCRETE_INSTRUCTION(TaggedToINoSSE2, "tagged-to-i-nosse2")
DECLARE_HYDROGEN_ACCESSOR(UnaryOperation)
bool truncating() { return hydrogen()->CanTruncateToInt32(); }
};
class LSmiTag: public LTemplateInstruction<1, 1, 0> {
public:
explicit LSmiTag(LOperand* value) {
......@@ -2040,6 +2074,10 @@ class LNumberUntagD: public LTemplateInstruction<1, 1, 1> {
LOperand* value() { return inputs_[0]; }
LOperand* temp() { return temps_[0]; }
virtual bool ClobbersDoubleRegisters() const {
return false;
}
DECLARE_CONCRETE_INSTRUCTION(NumberUntagD, "double-untag")
DECLARE_HYDROGEN_ACCESSOR(Change);
};
......@@ -2380,6 +2418,30 @@ class LClampTToUint8: public LTemplateInstruction<1, 1, 1> {
};
// Truncating conversion from a tagged value to an int32.
class LClampTToUint8NoSSE2: public LTemplateInstruction<1, 1, 3> {
public:
LClampTToUint8NoSSE2(LOperand* unclamped,
LOperand* temp1,
LOperand* temp2,
LOperand* temp3) {
inputs_[0] = unclamped;
temps_[0] = temp1;
temps_[1] = temp2;
temps_[2] = temp3;
}
LOperand* unclamped() { return inputs_[0]; }
LOperand* scratch() { return temps_[0]; }
LOperand* scratch2() { return temps_[1]; }
LOperand* scratch3() { return temps_[2]; }
DECLARE_CONCRETE_INSTRUCTION(ClampTToUint8NoSSE2,
"clamp-t-to-uint8-nosse2")
DECLARE_HYDROGEN_ACCESSOR(UnaryOperation)
};
class LCheckNonSmi: public LTemplateInstruction<0, 1, 0> {
public:
explicit LCheckNonSmi(LOperand* value) {
......@@ -2742,6 +2804,7 @@ class LChunkBuilder BASE_EMBEDDED {
MUST_USE_RESULT LOperand* UseFixed(HValue* value, Register fixed_register);
MUST_USE_RESULT LOperand* UseFixedDouble(HValue* value,
XMMRegister fixed_register);
MUST_USE_RESULT LOperand* UseX87TopOfStack(HValue* value);
// A value that is guaranteed to be allocated to a register.
// Operand created by UseRegister is guaranteed to be live until the end of
......@@ -2827,6 +2890,8 @@ class LChunkBuilder BASE_EMBEDDED {
LInstruction* DoArithmeticT(Token::Value op,
HArithmeticBinaryOperation* instr);
LOperand* GetStoreKeyedValueOperand(HStoreKeyed* instr);
LPlatformChunk* chunk_;
CompilationInfo* info_;
HGraph* const graph_;
......
......@@ -2518,6 +2518,28 @@ void MacroAssembler::Ret(int bytes_dropped, Register scratch) {
}
void MacroAssembler::VerifyX87StackDepth(uint depth) {
// Make sure the floating point stack is either empty or has depth items.
ASSERT(depth <= 7);
// The top-of-stack (tos) is 7 if there is one item pushed.
int tos = (8 - depth) % 8;
const int kTopMask = 0x3800;
push(eax);
fwait();
fnstsw_ax();
and_(eax, kTopMask);
shr(eax, 11);
cmp(eax, Immediate(tos));
Label all_ok;
j(equal, &all_ok);
Check(equal, "Unexpected FPU stack depth after instruction");
bind(&all_ok);
fnclex();
pop(eax);
}
void MacroAssembler::Drop(int stack_elements) {
if (stack_elements > 0) {
add(esp, Immediate(stack_elements * kPointerSize));
......
......@@ -807,6 +807,8 @@ class MacroAssembler: public Assembler {
return code_object_;
}
// Insert code to verify that the x87 stack has the specified depth (0-7)
void VerifyX87StackDepth(uint depth);
// ---------------------------------------------------------------------------
// StatsCounter support
......
......@@ -1788,7 +1788,7 @@ STATIC_ASSERT(DoubleRegister::kMaxNumAllocatableRegisters >=
bool LAllocator::TryAllocateFreeReg(LiveRange* current) {
LifetimePosition free_until_pos[DoubleRegister::kMaxNumAllocatableRegisters];
for (int i = 0; i < DoubleRegister::kMaxNumAllocatableRegisters; i++) {
for (int i = 0; i < num_registers_; i++) {
free_until_pos[i] = LifetimePosition::MaxPosition();
}
......@@ -1880,7 +1880,7 @@ void LAllocator::AllocateBlockedReg(LiveRange* current) {
LifetimePosition use_pos[DoubleRegister::kMaxNumAllocatableRegisters];
LifetimePosition block_pos[DoubleRegister::kMaxNumAllocatableRegisters];
for (int i = 0; i < DoubleRegister::NumAllocatableRegisters(); i++) {
for (int i = 0; i < num_registers_; i++) {
use_pos[i] = block_pos[i] = LifetimePosition::MaxPosition();
}
......
......@@ -1495,6 +1495,8 @@ class HeapNumber: public HeapObject {
static const int kExponentBits = 11;
static const int kExponentBias = 1023;
static const int kExponentShift = 20;
static const int kInfinityOrNanExponent =
(kExponentMask >> kExponentShift) - kExponentBias;
static const int kMantissaBitsInTopWord = 20;
static const int kNonMantissaBitsInTopWord = 12;
......
This diff is collapsed.
......@@ -27,12 +27,15 @@
// Flags: --allow-natives-syntax
var pixels = new Uint8ClampedArray(8);
var pixels = new Uint8ClampedArray(11);
function f() {
for (var i = 0; i < 8; i++) {
pixels[i] = (i * 1.1);
}
pixels[8] = 255.5;
pixels[9] = NaN;
pixels[10] = -0.5;
return pixels[1] + pixels[6];
}
......@@ -42,3 +45,6 @@ assertEquals(6, pixels[5]);
%OptimizeFunctionOnNextCall(f);
f();
assertEquals(6, pixels[5]);
assertEquals(255, pixels[8]);
assertEquals(0, pixels[9]);
assertEquals(0, pixels[10]);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment