void findDeadBlockEdges(Blocks &blocks) { /* Run through all blocks and find edges that are logically dead and will * never be taken. * * Currently, this is limited to one special case that occurs in scripts * compiled by the original BioWare NWScript compiler (at least in NWN and * KotOR): short-circuiting in if (x || y) conditionals. The original BioWare * compiler has a bug where it generates a JZ instead of a JMP, creating a * true branch that will never be taken and effectively disabling short- * circuiting. I.e. both x and y will always be evaluated; when x is true, * y will still be evaluated afterwards. * * We use very simple pattern-matching here. This is enough to find most * occurances of this case, but not all. */ for (Blocks::iterator b = blocks.begin(); b != blocks.end(); ++b) { if (!isTopStackJumper(*b) || (b->instructions.size() != 2) || b->parents.empty()) continue; /* Look through all parents of this block and make sure they fit the * pattern as well. They also all need to jump to this block with the * same branch edge (true or false). */ size_t parentEdge = SIZE_MAX; for (std::vector<const Block *>::const_iterator p = b->parents.begin(); p != b->parents.end(); ++p) { if (!isTopStackJumper(**p, &*b, &parentEdge)) { parentEdge = SIZE_MAX; break; } } if (parentEdge == SIZE_MAX) continue; assert(parentEdge < 2); /* We have now established that * 1) This block checks whether the top of the stack is == 0 * 2) All parent blocks check whether the top of the stack is == 0 * 3) All parent blocks jump with the same branch edge into this block * * Therefore, this block must also always follow the exact same edge. * This means the other edge is logically dead. */ b->childrenTypes[1 - parentEdge] = kBlockEdgeTypeDead; } }
void findDeadBlockEdges(Blocks &blocks) { /* Run through all blocks and find edges that are logically dead and will * never be taken. * * Currently, this is limited to one special case that occurs in scripts * compiled by the original BioWare NWScript compiler (at least in NWN and * KotOR): short-circuiting in if (x || y) conditionals. The original BioWare * compiler has a bug where it generates a JZ instead of a JMP, creating a * true branch that will never be taken and effectively disabling short- * circuiting. I.e. both x and y will always be evaluated; when x is true, * y will still be evaluated afterwards. * * We use very simple pattern-matching here. This is enough to find most * occurrences of this case, but not all. * * For example, this is the control flow diagram for the bytecode, as * compiled by the original BioWare compiler, for * * if ((global_variable == 1) || (global_variable == 3)) * * . * | * V * .-------------------. * | CPTOPBP -4 4 | * | CONSTI 1 | * | EQII | * | CPTOPSP -4 4 | * | JZ | * '-------------------' * (true)| |(false) * | V (1) * | .--------------------. * | | CPTOPSP -4 4 | * | | JZ | (4) * | '--------------------' * | (false)| |(true) * | (2) | | (3) * V V | * .-------------------. | * | CPTOPBP -4 4 | | * | CONSTI 3 | | * | EQII | | * '-------------------' | * | | * V | * .-------------------. | * | LOGORII -4 4 | | * | JZ |<-' * '-------------------' * (true) | |(false) * ' ' * * "CPTOPSP -4 4" takes the top element on the stack and, without * popping it, pushes it again onto the top, creating a duplicate. * * When taking the false branch at (1) (which means that the variable * *is* equal to 1), we have already established that the top element * on the stack (which is getting copied a few times, so it's not * vanishing) is of a certain value. This means that the false branch * at (2) has to be taken as well. The true branch at (3) can't ever * be taken, and is therefore logically dead. * * Moreover, if the true branch at (3) would have been taken, this * had resulted in a stack smash, because JZ consumes a stack element, * and the LOGORII would now be one element short. * * Essentially, the whole block at (4) evaluates to a NOP. * * How this *should* have been compiled is thusly: * * . * | * V * .-------------------. * | CPTOPBP -4 4 | * | CONSTI 1 | * | EQII | * | CPTOPSP -4 4 | * | JZ | * '-------------------' * (true)| |(false) * | V * | .--------------------. * | | CPTOPSP -4 4 | (5) * | | JMP | * | '--------------------' * | | * | | * V | * .-------------------. | * | CPTOPBP -4 4 | | * | CONSTI 3 | | * | EQII | | * '-------------------' | * | | * V (6) | * .-------------------. | * | LOGORII -4 4 | | * | JZ |<-' * '-------------------' * (true) | |(false) * ' ' * * In the block at (5), the top element is now copied, and the code * jumps unconditionally to the LOGORII block at (6). In contrast * to JZ, JMP does not pop an element from the stack. The LOGORII * has enough elements to do its comparison. * * This is exactly what the OpenKnights compiler does. And this has * been fixed by BioWare by the time of Neverwinter Nights 2 as well. * * The short-circuiting && construct does not seem to have this fault. */ for (Blocks::iterator b = blocks.begin(); b != blocks.end(); ++b) { if (!isTopStackJumper(*b) || (b->instructions.size() != 2) || b->parents.empty()) continue; /* Look through all parents of this block and make sure they fit the * pattern as well. They also all need to jump to this block with the * same branch edge (true or false). */ size_t parentEdge = SIZE_MAX; for (std::vector<const Block *>::const_iterator p = b->parents.begin(); p != b->parents.end(); ++p) { if (!isTopStackJumper(**p, &*b, &parentEdge)) { parentEdge = SIZE_MAX; break; } } if (parentEdge == SIZE_MAX) continue; assert(parentEdge < 2); /* We have now established that * 1) This block checks whether the top of the stack is == 0 * 2) All parent blocks check whether the top of the stack is == 0 * 3) All parent blocks jump with the same branch edge into this block * * Therefore, this block must also always follow the exact same edge. * This means the other edge is logically dead. */ b->childrenTypes[1 - parentEdge] = kBlockEdgeTypeDead; } }