pull out specialize_for_jit()
Not too tricky, but I did have to slightly work around
not having this be a member function of Builder. Seems
healthy enough though, nothing terribly ugly or dangerous.
Still TODO, unit test specialize. Probably another CL.
Change-Id: Ia547ae010940754fa7bb8417119416762597cb4c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/281045
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Herb Derby <herb@google.com>
diff --git a/src/core/SkVM.cpp b/src/core/SkVM.cpp
index cddffaf..68726f2 100644
--- a/src/core/SkVM.cpp
+++ b/src/core/SkVM.cpp
@@ -423,62 +423,69 @@
}
}
- std::vector<OptimizedInstruction> Builder::optimize(bool for_jit) const {
- // If requested, first specialize for our JIT backend.
- auto specialize_for_jit = [&]() -> std::vector<Instruction> {
- Builder specialized;
- for (Val i = 0; i < (Val)fProgram.size(); i++) {
- Instruction inst = fProgram[i];
+ void specialize_for_jit(std::vector<Instruction>* program) {
+ Builder specialized;
+ for (Val i = 0; i < (Val)program->size(); i++) {
+ Instruction inst = (*program)[i];
- #if defined(SK_CPU_X86)
- switch (Op imm_op; inst.op) {
- default: break;
+ #if defined(SK_CPU_X86)
+ auto is_imm = [&](Val id, int* bits) {
+ *bits = (*program)[id].immy;
+ return (*program)[id].op == Op::splat;
+ };
- case Op::add_f32: imm_op = Op::add_f32_imm; goto try_imm_x_and_y;
- case Op::mul_f32: imm_op = Op::mul_f32_imm; goto try_imm_x_and_y;
- case Op::min_f32: imm_op = Op::min_f32_imm; goto try_imm_x_and_y;
- case Op::max_f32: imm_op = Op::max_f32_imm; goto try_imm_x_and_y;
- case Op::bit_and: imm_op = Op::bit_and_imm; goto try_imm_x_and_y;
- case Op::bit_or: imm_op = Op::bit_or_imm ; goto try_imm_x_and_y;
- case Op::bit_xor: imm_op = Op::bit_xor_imm; goto try_imm_x_and_y;
+ switch (Op imm_op; inst.op) {
+ default: break;
- try_imm_x_and_y:
- if (int bits; this->allImm(inst.x, &bits)) {
- inst.op = imm_op;
- inst.x = inst.y;
- inst.y = NA;
- inst.immy = bits;
- } else if (int bits; this->allImm(inst.y, &bits)) {
- inst.op = imm_op;
- inst.y = NA;
- inst.immy = bits;
- } break;
+ case Op::add_f32: imm_op = Op::add_f32_imm; goto try_imm_x_and_y;
+ case Op::mul_f32: imm_op = Op::mul_f32_imm; goto try_imm_x_and_y;
+ case Op::min_f32: imm_op = Op::min_f32_imm; goto try_imm_x_and_y;
+ case Op::max_f32: imm_op = Op::max_f32_imm; goto try_imm_x_and_y;
+ case Op::bit_and: imm_op = Op::bit_and_imm; goto try_imm_x_and_y;
+ case Op::bit_or: imm_op = Op::bit_or_imm ; goto try_imm_x_and_y;
+ case Op::bit_xor: imm_op = Op::bit_xor_imm; goto try_imm_x_and_y;
- case Op::sub_f32:
- if (int bits; this->allImm(inst.y, &bits)) {
- inst.op = Op::sub_f32_imm;
- inst.y = NA;
- inst.immy = bits;
- } break;
+ try_imm_x_and_y:
+ if (int bits; is_imm(inst.x, &bits)) {
+ inst.op = imm_op;
+ inst.x = inst.y;
+ inst.y = NA;
+ inst.immy = bits;
+ } else if (int bits; is_imm(inst.y, &bits)) {
+ inst.op = imm_op;
+ inst.y = NA;
+ inst.immy = bits;
+ } break;
- case Op::bit_clear:
- if (int bits; this->allImm(inst.y, &bits)) {
- inst.op = Op::bit_and_imm;
- inst.y = NA;
- inst.immy = ~bits;
- } break;
- }
- #endif
- SkDEBUGCODE(Val id =) specialized.push(inst.op,
- inst.x,inst.y,inst.z,
- inst.immy,inst.immz);
- // If we replace single instructions with multiple, this will start breaking,
- // and we'll need a table to remap them like we have in optimize().
- SkASSERT(id == i);
+ case Op::sub_f32:
+ if (int bits; is_imm(inst.y, &bits)) {
+ inst.op = Op::sub_f32_imm;
+ inst.y = NA;
+ inst.immy = bits;
+ } break;
+
+ case Op::bit_clear:
+ if (int bits; is_imm(inst.y, &bits)) {
+ inst.op = Op::bit_and_imm;
+ inst.y = NA;
+ inst.immy = ~bits;
+ } break;
}
- return specialized.fProgram;
- };
- const std::vector<Instruction>& program = for_jit ? specialize_for_jit() : fProgram;
+ #endif
+ SkDEBUGCODE(Val id =) specialized.push(inst);
+ // If we replace single instructions with multiple, this will start breaking,
+ // and we'll need a table to remap them like we have in optimize().
+ SkASSERT(id == i);
+ }
+
+ *program = specialized.program();
+ }
+
+ std::vector<OptimizedInstruction> Builder::optimize(bool for_jit) const {
+ std::vector<Instruction> program = this->program();
+ if (for_jit) {
+ specialize_for_jit(&program);
+ }
std::vector<bool> live_instructions;
std::vector<Val> frontier;
@@ -663,9 +670,7 @@
// Most instructions produce a value and return it by ID,
// the value-producing instruction's own index in the program vector.
- Val Builder::push(Op op, Val x, Val y, Val z, int immy, int immz) {
- Instruction inst{op, x, y, z, immy, immz};
-
+ Val Builder::push(Instruction inst) {
// Basic common subexpression elimination:
// if we've already seen this exact Instruction, use it instead of creating a new one.
if (Val* id = fIndex.find(inst)) {
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index d1cc193..9bb4665 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -690,8 +690,11 @@
uint64_t hash() const;
+ Val push(Instruction);
private:
- Val push(Op, Val x, Val y=NA, Val z=NA, int immy=0, int immz=0);
+ Val push(Op op, Val x, Val y=NA, Val z=NA, int immy=0, int immz=0) {
+ return this->push(Instruction{op, x,y,z, immy,immz});
+ }
I32 _(I32a x) {
if (x.id != NA) {
@@ -725,6 +728,11 @@
std::vector<int> fStrides;
};
+ // Optimization passes and data structures normally used by Builder::optimize(),
+ // extracted here so they can be unit tested.
+
+ void specialize_for_jit(std::vector<Instruction>* program);
+
// Fill live and sinks each if non-null:
// - (*live)[id]: notes whether each input instruction is live
// - *sinks: an unsorted set of live instructions with side effects (stores, assert_true)