Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 51dc285

Browse files
Mike KleinSkia Commit-Bot
authored andcommitted
allow early returns
For posterity, here's my initial, wrong thinking: If we squint at "return foo" and read it as "result = foo; goto end_of_program", and then remember we can always skip forward jumps (and where's further forward than end of program?), early returns turn out to work just like a store. The reason this is wrong is that by the time we reach a final return, the entire mask stack has been popped back down to its original default ffffffff (active) state. But that return shouldn't override any prior returns. So that scheme isn't quite right. Instead we accumulate the result by disabling updates to lanes that have already returned. By the time we're done, all lanes should have hit _some_ active return, now asserted. Bug: skia:10852 Change-Id: I27b05f04a60ff4a5f2fe5f59bf398c3f7224a41b Reviewed-on: https://skia-review.googlesource.com/c/skia/+/327457 Commit-Queue: Mike Klein <mtklein@google.com> Reviewed-by: Brian Osman <brianosman@google.com>
1 parent 6cc9d8d commit 51dc285

File tree

4 files changed

+69
-15
lines changed

4 files changed

+69
-15
lines changed

src/core/SkRuntimeEffect.cpp

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,14 @@ static skvm::Color program_fn(skvm::Builder* p,
361361
std::vector<skvm::I32> cond_stack = { p->splat(0xffff'ffff) };
362362
std::vector<skvm::I32> mask_stack = cond_stack;
363363

364+
skvm::Color result = {
365+
p->splat(0.0f),
366+
p->splat(0.0f),
367+
p->splat(0.0f),
368+
p->splat(0.0f),
369+
};
370+
skvm::I32 result_locked_in = p->splat(0);
371+
364372
for (const uint8_t *ip = fn.code(), *end = ip + fn.size(); ip != end; ) {
365373
using Inst = SkSL::ByteCodeInstruction;
366374

@@ -637,21 +645,30 @@ static skvm::Color program_fn(skvm::Builder* p,
637645
} break;
638646

639647
case Inst::kReturn: {
640-
SkAssertResult(u8() == 4);
641-
// We'd like to assert that (ip == end) -> there is only one return, but ByteCode
642-
// always includes a kReturn/0 at the end of each function, as a precaution.
643-
SkASSERT(stack.size() >= 4);
644-
skvm::F32 a = pop(),
645-
b = pop(),
646-
g = pop(),
647-
r = pop();
648-
return { r, g, b, a };
648+
int count = u8();
649+
SkAssertResult(count == 4 || count == 0);
650+
651+
if (count == 4) {
652+
SkASSERT(stack.size() >= 4);
653+
654+
// Lane-by-lane, if we've already returned a value, that result is locked in;
655+
// later return instructions don't happen for that lane.
656+
skvm::I32 returns_here = bit_clear(mask_stack.back(),
657+
result_locked_in);
658+
659+
result.a = select(returns_here, pop(), result.a);
660+
result.b = select(returns_here, pop(), result.b);
661+
result.g = select(returns_here, pop(), result.g);
662+
result.r = select(returns_here, pop(), result.r);
663+
664+
result_locked_in |= returns_here;
665+
}
649666
} break;
650667
}
651668
}
652669

653-
SkUNREACHABLE;
654-
return {};
670+
assert_true(result_locked_in);
671+
return result;
655672
}
656673

657674
static sk_sp<SkData> get_xformed_uniforms(const SkRuntimeEffect* effect,

src/core/SkVM.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,9 @@ namespace skvm {
12191219
static inline Q14& operator|=(Q14& x, Q14a y) { return (x = x | y); }
12201220
static inline Q14& operator^=(Q14& x, Q14a y) { return (x = x ^ y); }
12211221

1222+
static inline I32 bit_clear(I32 x, I32a y) { return x->bit_clear(x,y); }
1223+
static inline I32 bit_clear(int x, I32 y) { return y->bit_clear(x,y); }
1224+
12221225
static inline I32 select(I32 cond, I32a t, I32a f) { return cond->select(cond,t,f); }
12231226
static inline F32 select(I32 cond, F32a t, F32a f) { return cond->select(cond,t,f); }
12241227
static inline Q14 select(Q14 cond, Q14a t, Q14a f) { return cond->select(cond,t,f); }

src/sksl/SkSLByteCodeGenerator.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,8 +1740,8 @@ void ByteCodeGenerator::writeIfStatement(const IfStatement& i) {
17401740
}
17411741

17421742
void ByteCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
1743-
if (fLoopCount || fConditionCount) {
1744-
fErrors.error(r.fOffset, "return not allowed inside conditional or loop");
1743+
if (fLoopCount) {
1744+
fErrors.error(r.fOffset, "return not allowed inside loop");
17451745
return;
17461746
}
17471747
int count = SlotCount(r.expression()->type());

tests/SkSLInterpreterTest.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -730,11 +730,45 @@ DEF_TEST(SkSLInterpreterRestrictFunctionCalls, r) {
730730
// Ensure that calls to undefined functions are not allowed (to prevent mutual recursion)
731731
expect_failure(r, "float foo(); float bar() { return foo(); } float foo() { return bar(); }");
732732

733-
// returns are not allowed inside conditionals (or loops, which are effectively the same thing)
734-
expect_failure(r, "float main(float x, float y) { if (x < y) { return x; } return y; }");
733+
// returns are not allowed inside loops
735734
expect_failure(r, "float main(float x) { while (x > 1) { return x; } return 0; }");
736735
}
737736

737+
DEF_TEST(SkSLInterpreterEarlyReturn, r) {
738+
// Unlike returns in loops, returns in conditionals should work.
739+
const char* src = "float main(float x, float y) { if (x < y) { return x; } return y; }";
740+
741+
SkSL::Compiler compiler;
742+
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(SkSL::Program::kGeneric_Kind,
743+
SkSL::String(src),
744+
SkSL::Program::Settings{});
745+
REPORTER_ASSERT(r, program);
746+
747+
std::unique_ptr<SkSL::ByteCode> byteCode = compiler.toByteCode(*program);
748+
REPORTER_ASSERT(r, byteCode);
749+
750+
// Our function should work fine via run().
751+
const SkSL::ByteCodeFunction* fun = byteCode->getFunction("main");
752+
float in[] = { 1.0f, 2.0f };
753+
float ret;
754+
REPORTER_ASSERT(r, byteCode->run(fun, in,2, &ret,1, nullptr,0));
755+
REPORTER_ASSERT(r, ret == 1.0f);
756+
757+
in[0] = 3.0f;
758+
REPORTER_ASSERT(r, byteCode->run(fun, in,2, &ret,1, nullptr,0));
759+
REPORTER_ASSERT(r, ret == 2.0f);
760+
761+
// Now same again via runStriped(), won't quite work yet.
762+
float xs[] = { 1.0f, 3.0f },
763+
ys[] = { 2.0f, 2.0f };
764+
float rets[2];
765+
float* ins[] = { xs, ys };
766+
float* outs[] = { rets+0, rets+1 } ;
767+
REPORTER_ASSERT(r, byteCode->runStriped(fun,2, ins,2, outs,1, nullptr,0));
768+
REPORTER_ASSERT(r, rets[0] == 1.0f);
769+
//REPORTER_ASSERT(r, rets[1] == 2.0f); // TODO: skia:10852, make striped early returns work.
770+
}
771+
738772
DEF_TEST(SkSLInterpreterArrayBounds, r) {
739773
// Out of bounds array access at compile time
740774
expect_failure(r, "float main(float x[4]) { return x[-1]; }");

0 commit comments

Comments
 (0)