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
1 change: 1 addition & 0 deletions rulesengine/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("smithy-java.module-conventions")
id("smithy-java.jmh-conventions")
}

description = "Implements the rules engine traits used to resolve endpoints"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.java.rulesengine;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import software.amazon.smithy.java.context.Context;

@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
public class TemplateResolutionBenchmark {

@Param({"3", "5", "9"})
public int segmentCount;

private BytecodeEvaluator resolveRegisters;
private BytecodeEvaluator buildRegisters;
private BytecodeEvaluator resolveProperties;
private BytecodeEvaluator buildProperties;

@Setup(Level.Trial)
public void setup() {
resolveRegisters = createEvaluator(createProgram(false, false));
buildRegisters = createEvaluator(createProgram(true, false));
resolveProperties = createEvaluator(createProgram(false, true));
buildProperties = createEvaluator(createProgram(true, true));
}

@Benchmark
public boolean resolveRegisters() {
return resolveRegisters.test(0);
}

@Benchmark
public boolean buildRegisters() {
return buildRegisters.test(0);
}

@Benchmark
public boolean resolveProperties() {
return resolveProperties.test(0);
}

@Benchmark
public boolean buildProperties() {
return buildProperties.test(0);
}

private Bytecode createProgram(boolean buildTemplate, boolean properties) {
BytecodeWriter writer = new BytecodeWriter();
int dynamicCount = segmentCount / 2;
RegisterDefinition[] registers = new RegisterDefinition[dynamicCount];
StringBuilder expected = new StringBuilder();

writer.markConditionStart();
if (buildTemplate) {
writer.writeByte(Opcodes.BUILD_TEMPLATE);
writer.writeByte(segmentCount);
}

for (int i = 0; i < segmentCount; i++) {
if ((i & 1) == 0) {
String literal = i == 0 ? "service." : ".";
expected.append(literal);
writeLiteral(writer, buildTemplate, literal);
} else {
int register = i / 2;
String value = "value" + register;
expected.append(value);
registers[register] = new RegisterDefinition(
"register" + register,
false,
properties ? Map.of("value", value) : value,
null,
false);
writeDynamic(writer, buildTemplate, properties, register);
}
}

if (!buildTemplate) {
writer.writeByte(Opcodes.RESOLVE_TEMPLATE);
writer.writeByte(segmentCount);
}
writeLoadConstant(writer, writer.getConstantIndex(expected.toString()));
writer.writeByte(Opcodes.STRING_EQUALS);
writer.writeByte(Opcodes.RETURN_VALUE);

return writer.build(registers, new RulesFunction[0], new int[] {-1, 1, -1}, 1);
}

private void writeLiteral(BytecodeWriter writer, boolean buildTemplate, String literal) {
int constant = writer.getConstantIndex(literal);
if (buildTemplate) {
writer.writeByte(TemplateSegmentType.LITERAL);
writer.writeShort(constant);
} else {
writeLoadConstant(writer, constant);
}
}

private void writeDynamic(BytecodeWriter writer, boolean buildTemplate, boolean property, int register) {
if (buildTemplate) {
writer.writeByte(property ? TemplateSegmentType.REGISTER_PROPERTY : TemplateSegmentType.REGISTER);
writer.writeByte(register);
if (property) {
writer.writeShort(writer.getConstantIndex("value"));
}
} else if (property) {
writer.writeByte(Opcodes.GET_PROPERTY_REG);
writer.writeByte(register);
writer.writeShort(writer.getConstantIndex("value"));
} else {
writer.writeByte(Opcodes.LOAD_REGISTER);
writer.writeByte(register);
}
}

private void writeLoadConstant(BytecodeWriter writer, int constant) {
if (constant < 256) {
writer.writeByte(Opcodes.LOAD_CONST);
writer.writeByte(constant);
} else {
writer.writeByte(Opcodes.LOAD_CONST_W);
writer.writeShort(constant);
}
}

private BytecodeEvaluator createEvaluator(Bytecode bytecode) {
RegisterFiller filler = RegisterFiller.of(bytecode, Collections.emptyMap());
BytecodeEvaluator evaluator = new BytecodeEvaluator(bytecode, new RulesExtension[0], filler);
evaluator.reset(Context.empty(), Collections.emptyMap());
return evaluator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* Offset Size Description
* ------ ---- -----------
* 0 4 Magic number (0x52554C45 = "RULE")
* 4 2 Version (rolling version number, currently 1)
* 4 2 Version (rolling version number, currently 2)
* 6 2 Condition count (unsigned short)
* 8 2 Result count (unsigned short)
* 10 2 Register count (unsigned short)
Expand Down Expand Up @@ -153,7 +153,7 @@
public final class Bytecode {

static final int MAGIC = 0x52554C45; // "RULE"
static final short VERSION = 1;
static final short VERSION = 2;
static final byte CONST_NULL = 0;
static final byte CONST_STRING = 1;
static final byte CONST_INTEGER = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

final class BytecodeCompiler {

private static final int MIN_BUILD_TEMPLATE_SEGMENTS = 3;

private final List<RulesExtension> extensions;
private final EndpointBddTrait bdd;
private final Map<String, Function<Context, Object>> builtinProviders;
Expand Down Expand Up @@ -204,48 +206,12 @@ private void compileEndpointUrl(Expression urlExpression) {
}
}

// 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("");
}
compileTemplateParts(parts, 1, hostEnd, afterScheme);

// 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);
}
compileTemplateParts(parts, pathPartIndex, parts.size(), null);
} else {
addLoadConst("");
}
Expand Down Expand Up @@ -273,6 +239,104 @@ private static boolean containsUriSpecialChars(List<Template.Part> parts) {
return false;
}

private void compileTemplateParts(
List<Template.Part> parts,
int start,
int end,
String leadingLiteral
) {
boolean hasLeadingLiteral = leadingLiteral != null && !leadingLiteral.isEmpty();
int segmentCount = end - start + (hasLeadingLiteral ? 1 : 0);
if (segmentCount == 0) {
addLoadConst("");
return;
}

if (segmentCount >= MIN_BUILD_TEMPLATE_SEGMENTS
&& tryCompileBuildTemplate(parts, start, end, leadingLiteral)) {
return;
}

if (hasLeadingLiteral) {
addLoadConst(leadingLiteral);
}
for (int i = start; i < end; i++) {
var part = parts.get(i);
if (part instanceof Template.Dynamic dynamic) {
compileExpression(dynamic.toExpression());
} else {
addLoadConst(part.toString());
}
}
if (segmentCount > 1) {
writer.writeByte(Opcodes.RESOLVE_TEMPLATE);
writer.writeByte(segmentCount);
}
}

private boolean tryCompileBuildTemplate(
List<Template.Part> parts,
int start,
int end,
String leadingLiteral
) {
var segments = new ArrayList<TemplateSegment>(end - start + 1);
if (leadingLiteral != null && !leadingLiteral.isEmpty()) {
segments.add(TemplateSegment.literal(leadingLiteral));
}

for (int i = start; i < end; i++) {
TemplateSegment segment = createTemplateSegment(parts.get(i));
if (segment == null) {
return false;
}
segments.add(segment);
}

if (segments.size() > 255) {
return false;
}

writer.writeByte(Opcodes.BUILD_TEMPLATE);
writer.writeByte(segments.size());
for (var segment : segments) {
writer.writeByte(segment.type());
switch (segment.type()) {
case TemplateSegmentType.LITERAL -> writer.writeShort(writer.getConstantIndex(segment.value()));
case TemplateSegmentType.REGISTER -> writer.writeByte(segment.register());
case TemplateSegmentType.REGISTER_PROPERTY -> {
writer.writeByte(segment.register());
writer.writeShort(writer.getConstantIndex(segment.value()));
}
default -> throw new IllegalStateException("Unexpected template segment type: " + segment.type());
}
}
return true;
}

private TemplateSegment createTemplateSegment(Template.Part part) {
if (part instanceof Template.Literal literal) {
return TemplateSegment.literal(literal.toString());
}
if (!(part instanceof Template.Dynamic dynamic)) {
return null;
}

Expression expression = dynamic.toExpression();
if (expression instanceof Reference ref) {
return TemplateSegment.register(registerAllocator.getRegister(ref.getName().toString()));
}
if (expression instanceof GetAttr getAttr
&& getAttr.getTarget() instanceof Reference ref
&& getAttr.getPath().size() == 1
&& getAttr.getPath().get(0) instanceof GetAttr.Part.Key key) {
return TemplateSegment.registerProperty(
registerAllocator.getRegister(ref.getName().toString()),
key.key().toString());
}
return null;
}

private void compileErrorRule(ErrorRule rule) {
compileExpression(rule.getError());
writer.writeByte(Opcodes.RETURN_ERROR);
Expand Down Expand Up @@ -630,18 +694,7 @@ private void compileLiteral(Literal literal) {
// Single dynamic expression, so just evaluate it
compileExpression(dynamic.toExpression());
} else {
// Multiple parts - need to concatenate
int expressionCount = 0;
for (var part : parts) {
if (part instanceof Template.Dynamic d) {
compileExpression(d.toExpression());
} else {
addLoadConst(part.toString());
}
expressionCount++;
}
writer.writeByte(Opcodes.RESOLVE_TEMPLATE);
writer.writeByte(expressionCount);
compileTemplateParts(parts, 0, parts.size(), null);
}
}
case TupleLiteral t -> {
Expand Down Expand Up @@ -708,6 +761,20 @@ private void addLoadConst(Object value) {
}
}

private record TemplateSegment(byte type, byte register, String value) {
static TemplateSegment literal(String value) {
return new TemplateSegment(TemplateSegmentType.LITERAL, (byte) 0, value);
}

static TemplateSegment register(byte register) {
return new TemplateSegment(TemplateSegmentType.REGISTER, register, null);
}

static TemplateSegment registerProperty(byte register, String property) {
return new TemplateSegment(TemplateSegmentType.REGISTER_PROPERTY, register, property);
}
}

private Bytecode buildProgram() {
var registerDefs = registerAllocator.getRegistry().toArray(new RegisterDefinition[0]);
var fns = usedFunctions.toArray(new RulesFunction[0]);
Expand Down
Loading
Loading