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

Commit d3494ed

Browse files
brianosmanSkia Commit-Bot
authored andcommitted
ByteCode: Support out params in local function calls
Change-Id: I1d133259264adfdc872b0f4aeaa9390363c46341 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222040 Reviewed-by: Mike Klein <mtklein@google.com> Commit-Queue: Brian Osman <brianosman@google.com>
1 parent 82774f8 commit d3494ed

File tree

4 files changed

+90
-17
lines changed

4 files changed

+90
-17
lines changed

src/sksl/SkSLByteCode.cpp

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ static const uint8_t* disassemble_instruction(const uint8_t* ip) {
146146
VECTOR_DISASSEMBLE(kRemainderF, "remainderf")
147147
VECTOR_DISASSEMBLE(kRemainderS, "remainders")
148148
VECTOR_DISASSEMBLE(kRemainderU, "remainderu")
149+
case ByteCodeInstruction::kReserve: printf("reserve %d", READ8()); break;
149150
case ByteCodeInstruction::kReturn: printf("return %d", READ8()); break;
150151
case ByteCodeInstruction::kScalarToMatrix: {
151152
int cols = READ8();
@@ -334,6 +335,7 @@ struct StackFrame {
334335
const uint8_t* fCode;
335336
const uint8_t* fIP;
336337
VValue* fStack;
338+
int fParameterCount;
337339
};
338340

339341
static F32 mix(F32 start, F32 end, F32 t) {
@@ -404,19 +406,16 @@ void innerRun(const ByteCode* byteCode, const ByteCodeFunction* f, VValue* stack
404406
break;
405407

406408
case ByteCodeInstruction::kCall: {
407-
// Precursor code has pushed all parameters to the stack. Update our bottom of
408-
// stack to point at the first parameter, and our sp to point past those parameters
409-
// (plus space for locals).
409+
// Precursor code reserved space for the return value, and pushed all parameters to
410+
// the stack. Update our bottom of stack to point at the first parameter, and our
411+
// sp to point past those parameters (plus space for locals).
410412
int target = READ8();
411413
const ByteCodeFunction* fun = byteCode->fFunctions[target].get();
412414
if (skvx::any(mask())) {
413-
frames.push_back({ code, ip, stack });
415+
frames.push_back({ code, ip, stack, fun->fParameterCount });
414416
ip = code = fun->fCode.data();
415417
stack = sp - fun->fParameterCount + 1;
416418
sp = stack + fun->fParameterCount + fun->fLocalCount - 1;
417-
} else {
418-
sp -= fun->fParameterCount;
419-
sp += fun->fReturnCount;
420419
}
421420
break;
422421
}
@@ -715,6 +714,10 @@ void innerRun(const ByteCode* byteCode, const ByteCodeFunction* f, VValue* stack
715714
VECTOR_BINARY_FN(kRemainderS, fSigned, vec_mod<I32>)
716715
VECTOR_BINARY_FN(kRemainderU, fUnsigned, vec_mod<U32>)
717716

717+
case ByteCodeInstruction::kReserve:
718+
sp += READ8();
719+
break;
720+
718721
case ByteCodeInstruction::kReturn: {
719722
int count = READ8();
720723
if (frames.empty()) {
@@ -742,14 +745,17 @@ void innerRun(const ByteCode* byteCode, const ByteCodeFunction* f, VValue* stack
742745
}
743746
return;
744747
} else {
745-
// When we were called, 'stack' was positioned at the old top-of-stack (where
746-
// our parameters were placed). So copy our return values to that same spot.
747-
memmove(stack, sp - count + 1, count * sizeof(VValue));
748-
749-
// Now move the stack pointer to the end of the just-pushed return values,
750-
// and restore everything else.
748+
// When we were called, the caller reserved stack space for their copy of our
749+
// return value, then 'stack' was positioned after that, where our parameters
750+
// were placed. Copy our return values to their reserved area.
751+
memcpy(stack - count, sp - count + 1, count * sizeof(VValue));
752+
753+
// Now move the stack pointer to the end of the passed-in parameters. This odd
754+
// calling convention requires the caller to pop the arguments after calling,
755+
// but allows them to store any out-parameters back during that unwinding.
756+
// After that sequence finishes, the return value will be the top of the stack.
751757
const StackFrame& frame(frames.back());
752-
sp = stack + count - 1;
758+
sp = stack + frame.fParameterCount - 1;
753759
stack = frame.fStack;
754760
code = frame.fCode;
755761
ip = frame.fIP;

src/sksl/SkSLByteCode.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ enum class ByteCodeInstruction : uint16_t {
9090
VECTOR(kRemainderF),
9191
VECTOR(kRemainderS),
9292
VECTOR(kRemainderU),
93+
// Followed by a byte indicating the number of slots to reserve on the stack (for later return)
94+
kReserve,
9395
// Followed by a byte indicating the number of slots being returned
9496
kReturn,
9597
// Followed by two bytes indicating columns and rows of matrix (2, 3, or 4 each).

src/sksl/SkSLByteCodeGenerator.cpp

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,15 +637,63 @@ void ByteCodeGenerator::writeIntrinsicCall(const FunctionCall& c) {
637637
}
638638

639639
void ByteCodeGenerator::writeFunctionCall(const FunctionCall& f) {
640-
for (const auto& arg : f.fArguments) {
641-
this->writeExpression(*arg);
642-
}
640+
// Builtins have simple signatures...
643641
if (f.fFunction.fBuiltin) {
642+
for (const auto& arg : f.fArguments) {
643+
this->writeExpression(*arg);
644+
}
644645
this->writeIntrinsicCall(f);
645646
return;
646647
}
648+
649+
// Otherwise, we may need to deal with out parameters, so the sequence is trickier...
650+
if (int returnCount = SlotCount(f.fType)) {
651+
this->write(ByteCodeInstruction::kReserve);
652+
this->write8(returnCount);
653+
}
654+
655+
int argCount = f.fArguments.size();
656+
std::vector<std::unique_ptr<LValue>> lvalues;
657+
for (int i = 0; i < argCount; ++i) {
658+
const auto& param = f.fFunction.fParameters[i];
659+
const auto& arg = f.fArguments[i];
660+
if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
661+
lvalues.emplace_back(this->getLValue(*arg));
662+
lvalues.back()->load();
663+
} else {
664+
this->writeExpression(*arg);
665+
}
666+
}
667+
647668
this->write(ByteCodeInstruction::kCall);
648669
fCallTargets.emplace_back(this, f.fFunction);
670+
671+
// After the called function returns, the stack will still contain our arguments. We have to
672+
// pop them (storing any out parameters back to their lvalues as we go). We glob together slot
673+
// counts for all parameters that aren't out-params, so we can pop them in one big chunk.
674+
int popCount = 0;
675+
auto pop = [&]() {
676+
if (popCount > 4) {
677+
this->write(ByteCodeInstruction::kPopN);
678+
this->write8(popCount);
679+
} else if (popCount > 0) {
680+
this->write(vector_instruction(ByteCodeInstruction::kPop, popCount));
681+
}
682+
popCount = 0;
683+
};
684+
685+
for (int i = argCount - 1; i >= 0; --i) {
686+
const auto& param = f.fFunction.fParameters[i];
687+
const auto& arg = f.fArguments[i];
688+
if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
689+
pop();
690+
lvalues.back()->store(true);
691+
lvalues.pop_back();
692+
} else {
693+
popCount += SlotCount(arg->fType);
694+
}
695+
}
696+
pop();
649697
}
650698

651699
void ByteCodeGenerator::writeIntLiteral(const IntLiteral& i) {

tests/SkSLInterpreterTest.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,23 @@ DEF_TEST(SkSLInterpreterFunctions, r) {
661661
REPORTER_ASSERT(r, fibOut == 13);
662662
}
663663

664+
DEF_TEST(SkSLInterpreterOutParams, r) {
665+
test(r,
666+
"void oneAlpha(inout half4 color) { color.a = 1; }"
667+
"void main(inout half4 color) { oneAlpha(color); }",
668+
0, 0, 0, 0, 0, 0, 0, 1);
669+
test(r,
670+
"half2 tricky(half x, half y, inout half2 color, half z) {"
671+
" color.xy = color.yx;"
672+
" return half2(x + y, z);"
673+
"}"
674+
"void main(inout half4 color) {"
675+
" half2 t = tricky(1, 2, color.rb, 5);"
676+
" color.ga = t;"
677+
"}",
678+
1, 2, 3, 4, 3, 3, 1, 5);
679+
}
680+
664681
DEF_TEST(SkSLInterpreterMathFunctions, r) {
665682
float value, expected;
666683

0 commit comments

Comments
 (0)