Skip to content

Commit c9cf3c3

Browse files
committed
Add fused SELECT_BOOL_REG to endpoints VM
Optimizes a couple hot instructions for S3 endpoints, but is a generic optimization. For S3, these are optimized: ``` _s3e_fips = ite(UseFIPS, "-fips", "") _s3e_ds = ite(UseDualStack, ".dualstack", "") ``` Without SELECT_BOOL_REG, each compiles to 5 opcodes: ``` LOAD_REGISTER UseFIPS JMP_IF_FALSE 5 LOAD_CONST "-fips" JUMP 2 LOAD_CONST "" ``` With SELECT_BOOL_REG, each becomes 1 opcode: ``` SELECT_BOOL_REG UseFIPS, "-fips", "" ``` The end result is that this speeds up S3 endpoint resolution consistently by 1-4% depending on the test case (smaller programs like vanilla endpoint resolution see bigger savings, larger programs see less savings).
1 parent dbfca8f commit c9cf3c3

5 files changed

Lines changed: 66 additions & 3 deletions

File tree

rulesengine/src/main/java/software/amazon/smithy/java/rulesengine/BytecodeCompiler.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,19 @@ public Void visitLibraryFunction(FunctionDefinition fn, List<Expression> args) {
328328
throw new RulesEvaluationError("ite requires exactly 3 arguments, got " + args.size());
329329
}
330330

331+
// Optimize: ite(boolRegister, constA, constB) -> SELECT_BOOL_REG
332+
if (args.get(0) instanceof Reference ref
333+
&& args.get(1) instanceof Literal litTrue
334+
&& args.get(2) instanceof Literal litFalse) {
335+
var trueVal = litTrue.toNode().toNode();
336+
var falseVal = litFalse.toNode().toNode();
337+
writer.writeByte(Opcodes.SELECT_BOOL_REG);
338+
writer.writeByte(registerAllocator.getRegister(ref.getName().toString()));
339+
writer.writeShort(writer.getConstantIndex(EndpointUtils.convertNode(trueVal, true)));
340+
writer.writeShort(writer.getConstantIndex(EndpointUtils.convertNode(falseVal, true)));
341+
return null;
342+
}
343+
331344
// Compile condition
332345
compileExpression(args.get(0));
333346

rulesengine/src/main/java/software/amazon/smithy/java/rulesengine/BytecodeDisassembler.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ final class BytecodeDisassembler {
8484
Map.entry(Opcodes.JMP_IF_FALSE, new InstructionDef("JMP_IF_FALSE", Show.JUMP_OFFSET)),
8585
Map.entry(Opcodes.JUMP, new InstructionDef("JUMP", Show.JUMP_OFFSET)),
8686
Map.entry(Opcodes.SUBSTRING_EQ, new InstructionDef("SUBSTRING_EQ", Show.SUBSTRING_EQ)),
87-
Map.entry(Opcodes.SPLIT_GET, new InstructionDef("SPLIT_GET", Show.SPLIT_GET)));
87+
Map.entry(Opcodes.SPLIT_GET, new InstructionDef("SPLIT_GET", Show.SPLIT_GET)),
88+
Map.entry(Opcodes.SELECT_BOOL_REG, new InstructionDef("SELECT_BOOL_REG", Show.SELECT_BOOL)));
8889

8990
private enum Show {
9091
CONST,
@@ -99,7 +100,8 @@ private enum Show {
99100
PROPERTY,
100101
ARG_COUNT,
101102
SUBSTRING_EQ,
102-
SPLIT_GET
103+
SPLIT_GET,
104+
SELECT_BOOL
103105
}
104106

105107
private record InstructionDef(String name, Show show) {
@@ -260,6 +262,7 @@ private void writeInstruction(StringBuilder s, BytecodeWalker walker, String ind
260262
||
261263
opcode == Opcodes.JNN_OR_POP
262264
|| (opcode == Opcodes.GET_PROPERTY_REG && i == 1)
265+
|| (opcode == Opcodes.SELECT_BOOL_REG && i >= 1)
263266
||
264267
(opcode == Opcodes.RESOLVE_TEMPLATE && i == 1)) {
265268
s.append(String.format("%5d", value));
@@ -380,6 +383,22 @@ private void appendSymbolicInfo(StringBuilder s, BytecodeWalker walker, Show sho
380383
}
381384
s.append(")[").append(index).append("]");
382385
}
386+
case SELECT_BOOL -> {
387+
int regIndex = walker.getOperand(0);
388+
int trueIdx = walker.getOperand(1);
389+
int falseIdx = walker.getOperand(2);
390+
if (regIndex >= 0 && regIndex < bytecode.getRegisterDefinitions().length) {
391+
s.append(bytecode.getRegisterDefinitions()[regIndex].name());
392+
}
393+
s.append(" ? ");
394+
if (trueIdx >= 0 && trueIdx < bytecode.getConstantPoolCount()) {
395+
s.append(formatConstant(bytecode.getConstant(trueIdx)));
396+
}
397+
s.append(" : ");
398+
if (falseIdx >= 0 && falseIdx < bytecode.getConstantPoolCount()) {
399+
s.append(formatConstant(bytecode.getConstant(falseIdx)));
400+
}
401+
}
383402
}
384403
}
385404

rulesengine/src/main/java/software/amazon/smithy/java/rulesengine/BytecodeEvaluator.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,16 @@ private Object runLoop(byte[] instructions, RulesFunction[] functions, Object[]
459459
var sgDelimiter = (String) constantPool[sgDelimIdx];
460460
push(EndpointUtils.splitGet(sgValue, sgDelimiter, sgIndex));
461461
}
462+
case Opcodes.SELECT_BOOL_REG -> {
463+
var selVal = registers[instructions[pc++] & 0xFF];
464+
int selTrue = ((instructions[pc] & 0xFF) << 8) | (instructions[pc + 1] & 0xFF);
465+
pc += 2;
466+
int selFalse = ((instructions[pc] & 0xFF) << 8) | (instructions[pc + 1] & 0xFF);
467+
pc += 2;
468+
push(selVal != null && selVal != Boolean.FALSE
469+
? constantPool[selTrue]
470+
: constantPool[selFalse]);
471+
}
462472
default -> throw new RulesEvaluationError("Unknown rules engine instruction: " + opcode, pc);
463473
}
464474
}

rulesengine/src/main/java/software/amazon/smithy/java/rulesengine/BytecodeWalker.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public int getOperandCount() {
9696
case Opcodes.GET_PROPERTY_REG, Opcodes.GET_INDEX_REG, Opcodes.RESOLVE_TEMPLATE,
9797
Opcodes.GET_NEGATIVE_INDEX_REG ->
9898
2;
99-
case Opcodes.SUBSTRING, Opcodes.SPLIT_GET -> 3;
99+
case Opcodes.SUBSTRING, Opcodes.SPLIT_GET, Opcodes.SELECT_BOOL_REG -> 3;
100100
case Opcodes.SUBSTRING_EQ -> 5;
101101
default -> -1;
102102
};
@@ -206,6 +206,16 @@ public int getOperand(int index) {
206206
return code.get(pc + 4) & 0xFF; // index (will be cast to signed byte by caller)
207207
}
208208
break;
209+
210+
case Opcodes.SELECT_BOOL_REG:
211+
if (index == 0) {
212+
return code.get(pc + 1) & 0xFF; // register
213+
} else if (index == 1) {
214+
return ((code.get(pc + 2) & 0xFF) << 8) | (code.get(pc + 3) & 0xFF); // true const
215+
} else if (index == 2) {
216+
return ((code.get(pc + 4) & 0xFF) << 8) | (code.get(pc + 5) & 0xFF); // false const
217+
}
218+
break;
209219
}
210220

211221
throw new IllegalArgumentException("Invalid operand index " + index + " for opcode " + opcode);
@@ -242,6 +252,7 @@ public static int getInstructionLength(byte opcode) {
242252
3;
243253
case Opcodes.RESOLVE_TEMPLATE, Opcodes.GET_PROPERTY_REG, Opcodes.SUBSTRING -> 4;
244254
case Opcodes.SPLIT_GET -> 5;
255+
case Opcodes.SELECT_BOOL_REG -> 6;
245256
case Opcodes.SUBSTRING_EQ -> 7;
246257
default -> -1;
247258
};

rulesengine/src/main/java/software/amazon/smithy/java/rulesengine/Opcodes.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,4 +487,14 @@ private Opcodes() {}
487487
* <p>Index is signed: positive = from start, negative = from end (-1 = last)
488488
*/
489489
public static final byte SPLIT_GET = 49;
490+
491+
/**
492+
* Select one of two constant values based on a boolean register.
493+
* Fuses ite(register, constA, constB) into a single opcode.
494+
*
495+
* <p>Stack: [...] => [..., value]
496+
*
497+
* <p><code>SELECT_BOOL_REG [register:byte] [true-const:short] [false-const:short]</code>
498+
*/
499+
public static final byte SELECT_BOOL_REG = 50;
490500
}

0 commit comments

Comments
 (0)