Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,49 @@ public Object vanillaAccessPointArn(ParamState params) {
public Object s3OutpostsVanilla(ParamState params) {
return params.resolver.resolveEndpoint(params.s3OutpostsVanillaParams);
}

/**
* Cycles through different bucket names on each invocation to defeat the URI cache hot-slot.
* This measures the cost of URI construction on cache miss.
*/
@State(Scope.Thread)
public static class VaryingBucketState {
private static final int BUCKET_COUNT = 64;
private EndpointResolverParams[] paramVariants;
private int index;

@Setup
public void setup(ParamState params) {
boolean canned = "canned".equals(params.paramMode);
var client = SharedResolver.CLIENT;
var getObject = SharedResolver.GET_OBJECT;
paramVariants = new EndpointResolverParams[BUCKET_COUNT];
for (int i = 0; i < BUCKET_COUNT; i++) {
String bucket = "bucket-" + i;
paramVariants[i] = ParamState.buildParams(client,
getObject,
canned,
Map.of("Bucket", Document.of(bucket), "Key", Document.of("key")),
"us-west-2",
Map.of("Accelerate",
false,
"Bucket",
bucket,
"ForcePathStyle",
false,
"Region",
"us-west-2",
"UseDualStack",
false,
"UseFIPS",
false));
}
}
}

@Benchmark
public Object vanillaVirtualAddressingVaryingBucket(ParamState params, VaryingBucketState varying) {
var p = varying.paramVariants[varying.index++ & (VaryingBucketState.BUCKET_COUNT - 1)];
return params.resolver.resolveEndpoint(p);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.java.rulesengine;

/**
* Lightweight PropertyGetter backed by parallel key/value arrays.
* More efficient than Map for small fixed-key lookups (linear scan beats hashing for ~4 entries).
*/
record ArrayPropertyGetter(String[] keys, Object[] values) implements PropertyGetter {
@Override
public Object getProperty(String name) {
for (int i = 0; i < keys.length; i++) {
if (name.equals(keys[i])) {
return values[i];
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,17 @@ public final class Bytecode {
private final int[] hardRequiredIndices;
private final Map<String, Integer> inputRegisterMap;

// Inline condition types for fast BDD evaluation.
static final byte COND_ISSET = 1;
static final byte COND_IS_TRUE = 2;
static final byte COND_IS_FALSE = 3;
static final byte COND_NOT_SET = 4;
static final byte COND_STRING_EQ_REG_CONST = 5;

// Condition classification arrays for inline BDD evaluation
final byte[] conditionTypes;
final int[] conditionOperands;

private Bdd bdd;

Bytecode(
Expand Down Expand Up @@ -230,6 +241,67 @@ public final class Bytecode {
this.hardRequiredIndices = findRequiredIndicesWithoutDefaultsOrBuiltins(registerDefinitions);
this.inputRegisterMap = createInputRegisterMap(registerDefinitions);
this.version = version;

// Classify conditions for inline BDD evaluation
this.conditionTypes = new byte[conditionOffsets.length];
this.conditionOperands = new int[conditionOffsets.length];
classifyConditions();
}

private void classifyConditions() {
int len = bytecode.length;
for (int i = 0; i < conditionOffsets.length; i++) {
int offset = conditionOffsets[i];

// 2-byte opcode patterns: <opcode> <register> <return> (need offset+2 in bounds)
if (offset + 2 < len) {
int firstOpcode = bytecode[offset] & 0xFF;
int reg = bytecode[offset + 1] & 0xFF;
int next = bytecode[offset + 2] & 0xFF;
// Only inline conditions that end with RETURN_VALUE (no binding).
// Conditions with SET_REG_RETURN have a side effect (register write) that
// the inline path cannot replicate — they must go through full bytecode eval.
boolean isReturn = next == (Opcodes.RETURN_VALUE & 0xFF);

if (isReturn) {
switch (firstOpcode) {
case Opcodes.TEST_REGISTER_ISSET & 0xFF -> {
conditionTypes[i] = COND_ISSET;
conditionOperands[i] = reg;
continue;
}
case Opcodes.TEST_REGISTER_IS_TRUE & 0xFF -> {
conditionTypes[i] = COND_IS_TRUE;
conditionOperands[i] = reg;
continue;
}
case Opcodes.TEST_REGISTER_IS_FALSE & 0xFF -> {
conditionTypes[i] = COND_IS_FALSE;
conditionOperands[i] = reg;
continue;
}
case Opcodes.TEST_REGISTER_NOT_SET & 0xFF -> {
conditionTypes[i] = COND_NOT_SET;
conditionOperands[i] = reg;
continue;
}
default -> {
}
}
}
}

// 4-byte opcode pattern: STRING_EQUALS_REG_CONST [reg:1] [const:2] <return>
if (offset + 4 < len && (bytecode[offset] & 0xFF) == (Opcodes.STRING_EQUALS_REG_CONST & 0xFF)) {
int reg = bytecode[offset + 1] & 0xFF;
int constIdx = ((bytecode[offset + 2] & 0xFF) << 8) | (bytecode[offset + 3] & 0xFF);
int next = bytecode[offset + 4] & 0xFF;
if (next == (Opcodes.RETURN_VALUE & 0xFF)) {
conditionTypes[i] = COND_STRING_EQ_REG_CONST;
conditionOperands[i] = (constIdx << 8) | reg;
}
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,14 @@ Bytecode compile() {

private void compileCondition(Condition condition) {
compileExpression(condition.getFunction());
condition.getResult().ifPresent(result -> {
byte register = registerAllocator.getOrAllocateRegister(result.toString());
writer.writeByte(Opcodes.SET_REGISTER);
var result = condition.getResult();
if (result.isPresent()) {
byte register = registerAllocator.getOrAllocateRegister(result.get().toString());
writer.writeByte(Opcodes.SET_REG_RETURN);
writer.writeByte(register);
});
writer.writeByte(Opcodes.RETURN_VALUE);
} else {
writer.writeByte(Opcodes.RETURN_VALUE);
}
}

private void compileEndpointRule(EndpointRule rule) {
Expand Down Expand Up @@ -150,7 +152,7 @@ private void compileEndpointRule(EndpointRule rule) {
compileMapCreation(e.getProperties().size());
}

compileExpression(e.getUrl());
compileEndpointUrl(e.getUrl());

// Add the return endpoint instruction
writer.writeByte(Opcodes.RETURN_ENDPOINT);
Expand All @@ -164,6 +166,95 @@ private void compileEndpointRule(EndpointRule rule) {
writer.writeByte(packed);
}

/**
* Compile the URL expression for an endpoint, attempting to decompose it into scheme/host/path
* so that BUILD_URI can construct a SmithyUri directly without string-to-URI parsing.
*
* <p>Falls back to compileExpression (producing a String) if the URL can't be decomposed.
*/
private void compileEndpointUrl(Expression urlExpression) {
// Only optimize StringLiteral templates with multiple parts where the first part
// is a literal starting with a scheme (e.g., "https://...")
if (urlExpression instanceof StringLiteral sl) {
var template = sl.value();
var parts = template.getParts();
if (parts.size() > 1 && parts.getFirst() instanceof Template.Literal firstLit) {
String firstStr = firstLit.toString();
int schemeEnd = firstStr.indexOf("://");
if (schemeEnd > 0) {
String scheme = firstStr.substring(0, schemeEnd);
String afterScheme = firstStr.substring(schemeEnd + 3);

// Find where path starts: look for a part that is "/" or starts with "/"
// The host is everything between "://" and the first "/" separator
// In most S3 templates, there's no explicit "/" — the path is empty
// In some, there's a "/" part followed by bucket + url.path
int pathPartIndex = -1;
for (int i = 0; i < parts.size(); i++) {
if (parts.get(i) instanceof Template.Literal lit && lit.toString().startsWith("/")
&& i > 0) {
pathPartIndex = i;
break;
}
}

// Compile host parts: afterScheme + parts[1..pathPartIndex)
int hostPartCount = 0;
if (!afterScheme.isEmpty()) {
addLoadConst(afterScheme);
hostPartCount++;
}
int hostEnd = pathPartIndex > 0 ? pathPartIndex : parts.size();
for (int i = 1; i < hostEnd; i++) {
var part = parts.get(i);
if (part instanceof Template.Dynamic d) {
compileExpression(d.toExpression());
} else {
addLoadConst(part.toString());
}
hostPartCount++;
}
// Resolve host template to a single string
if (hostPartCount == 1) {
// Already a single value on stack
} else if (hostPartCount > 1) {
writer.writeByte(Opcodes.RESOLVE_TEMPLATE);
writer.writeByte(hostPartCount);
} else {
addLoadConst("");
}

// Compile path parts
if (pathPartIndex > 0) {
int pathPartCount = 0;
for (int i = pathPartIndex; i < parts.size(); i++) {
var part = parts.get(i);
if (part instanceof Template.Dynamic d) {
compileExpression(d.toExpression());
} else {
addLoadConst(part.toString());
}
pathPartCount++;
}
if (pathPartCount > 1) {
writer.writeByte(Opcodes.RESOLVE_TEMPLATE);
writer.writeByte(pathPartCount);
}
} else {
addLoadConst("");
}

// BUILD_URI pops host and path, pushes SmithyUri
writer.writeByte(Opcodes.BUILD_URI);
writer.writeShort(writer.getConstantIndex(scheme));
return;
}
}
}
// Fallback: compile as regular string expression
compileExpression(urlExpression);
}

private void compileErrorRule(ErrorRule rule) {
compileExpression(rule.getError());
writer.writeByte(Opcodes.RETURN_ERROR);
Expand Down Expand Up @@ -288,6 +379,20 @@ public Void visitStringEquals(Expression left, Expression right) {
}
}
}
if (left instanceof Reference ref && right instanceof StringLiteral sl
&& sl.value().getParts().size() == 1) {
writer.writeByte(Opcodes.STRING_EQUALS_REG_CONST);
writer.writeByte(registerAllocator.getRegister(ref.getName().toString()));
writer.writeShort(writer.getConstantIndex(sl.value().getParts().get(0).toString()));
return null;
}
if (right instanceof Reference ref && left instanceof StringLiteral sl
&& sl.value().getParts().size() == 1) {
writer.writeByte(Opcodes.STRING_EQUALS_REG_CONST);
writer.writeByte(registerAllocator.getRegister(ref.getName().toString()));
writer.writeShort(writer.getConstantIndex(sl.value().getParts().get(0).toString()));
return null;
}
compileExpression(left);
compileExpression(right);
writer.writeByte(Opcodes.STRING_EQUALS);
Expand Down Expand Up @@ -519,7 +624,14 @@ private void compileLiteral(Literal literal) {
compileLiteral(e.getValue()); // value then key to make popping ordered
addLoadConst(e.getKey().toString());
}
compileMapCreation(r.members().size());
int size = r.members().size();
if (size <= 8) {
// Small records: PropertyGetter with linear scan beats Map hashing
writer.writeByte(Opcodes.STRUCTN);
writer.writeByte(size);
} else {
compileMapCreation(size);
}
}
case BooleanLiteral b -> addLoadConst(b.value().getValue());
case IntegerLiteral i -> addLoadConst(i.toNode().expectNumberNode().getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ final class BytecodeDisassembler {
Map.entry(Opcodes.JMP_IF_FALSE, new InstructionDef("JMP_IF_FALSE", Show.JUMP_OFFSET)),
Map.entry(Opcodes.JUMP, new InstructionDef("JUMP", Show.JUMP_OFFSET)),
Map.entry(Opcodes.SUBSTRING_EQ, new InstructionDef("SUBSTRING_EQ", Show.SUBSTRING_EQ)),
Map.entry(Opcodes.SPLIT_GET, new InstructionDef("SPLIT_GET", Show.SPLIT_GET)));
Map.entry(Opcodes.SPLIT_GET, new InstructionDef("SPLIT_GET", Show.SPLIT_GET)),
Map.entry(Opcodes.STRING_EQUALS_REG_CONST, new InstructionDef("STRING_EQUALS_REG_CONST", Show.REG_CONST)),
Map.entry(Opcodes.SET_REG_RETURN, new InstructionDef("SET_REG_RETURN", Show.REGISTER)),
Map.entry(Opcodes.BUILD_URI, new InstructionDef("BUILD_URI", Show.CONST)),
Map.entry(Opcodes.STRUCTN, new InstructionDef("STRUCTN", Show.NUMBER)));

private enum Show {
CONST,
Expand All @@ -99,7 +103,8 @@ private enum Show {
PROPERTY,
ARG_COUNT,
SUBSTRING_EQ,
SPLIT_GET
SPLIT_GET,
REG_CONST
}

private record InstructionDef(String name, Show show) {
Expand Down Expand Up @@ -257,11 +262,9 @@ private void writeInstruction(StringBuilder s, BytecodeWalker walker, String ind
int value = walker.getOperand(i);
// Format based on operand width
if (opcode == Opcodes.LOAD_CONST_W || opcode == Opcodes.GET_PROPERTY
||
opcode == Opcodes.JNN_OR_POP
|| opcode == Opcodes.JNN_OR_POP
|| (opcode == Opcodes.GET_PROPERTY_REG && i == 1)
||
(opcode == Opcodes.RESOLVE_TEMPLATE && i == 1)) {
|| (opcode == Opcodes.STRING_EQUALS_REG_CONST && i == 1)) {
s.append(String.format("%5d", value));
} else {
s.append(String.format("%3d", value));
Expand Down Expand Up @@ -380,6 +383,17 @@ private void appendSymbolicInfo(StringBuilder s, BytecodeWalker walker, Show sho
}
s.append(")[").append(index).append("]");
}
case REG_CONST -> {
int regIndex = walker.getOperand(0);
int constIdx = walker.getOperand(1);
if (regIndex >= 0 && regIndex < bytecode.getRegisterDefinitions().length) {
s.append(bytecode.getRegisterDefinitions()[regIndex].name());
}
s.append(" == ");
if (constIdx >= 0 && constIdx < bytecode.getConstantPoolCount()) {
s.append(formatConstant(bytecode.getConstant(constIdx)));
}
}
}
}

Expand Down
Loading
Loading