Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -0,0 +1,7 @@
{
"type": "feature",
"description": "Add new build transform CompileBddFromEndpointRuleSet",
"pull_requests": [
"[#2953](https://github.com/smithy-lang/smithy/pull/2953)"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ on the scenario.
This trait is experimental and subject to change.

Summary
A Binary `Decision Diagram (BDD) <https://en.wikipedia.org/wiki/Binary_decision_diagram>`_ representation of
A `Binary Decision Diagram (BDD) <https://en.wikipedia.org/wiki/Binary_decision_diagram>`_ representation of
endpoint rules that is more compact and efficient at runtime than the decision-tree-based EndpointRuleSet trait.
Trait selector
``service``
Expand All @@ -108,8 +108,11 @@ runtime performance and reduced artifact sizes.

.. note::

The ``endpointBdd`` trait can be generated from an ``endpointRuleSet`` trait through compilation. Services may
provide either trait, with ``endpointBdd`` preferred for production use due to its performance characteristics.
The ``endpointBdd`` trait can be generated from an ``endpointRuleSet`` trait through compilation.
To generate the ``endpointBdd`` trait for a service, add the
:ref:`compileBddFromEndpointRuleSet <compileBddFromEndpointRuleSet-transform>` transform to the
``smithy-build.json`` file. Services may provide either trait, with ``endpointBdd`` preferred for
production use due to its performance characteristics.

The ``endpointBdd`` structure has the following properties:

Expand Down
23 changes: 23 additions & 0 deletions docs/source-2.0/guides/smithy-build-json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,29 @@ Only the following shape type changes are supported:

.. seealso:: :ref:`changeStringEnumsToEnumShapes`

.. _compileBddFromEndpointRuleSet-transform:

compileBddFromEndpointRuleSet
-----------------------------

This transform compiles `Binary Decision Diagram (BDD) <https://en.wikipedia.org/wiki/Binary_decision_diagram>`_
from service shape's :ref:`@endpointRuleSet <smithy.rules#endpointRuleSet-trait>` trait and attaches
the compiled :ref:`@endpointBdd <smithy.rules#endpointBdd-trait>` trait to the service shape.

.. code-block:: json

{
"version": "1.0",
"projections": {
"exampleProjection": {
"transforms": [
{
"name": "compileBddFromEndpointRuleSet"
}
]
}
}
}

.. _excludeShapesBySelector-transform:

Expand Down
2 changes: 2 additions & 0 deletions smithy-build/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ extra["moduleName"] = "software.amazon.smithy.build"
dependencies {
api(project(":smithy-utils"))
api(project(":smithy-model"))
api(project(":smithy-rules-engine"))
api(project(":smithy-aws-endpoints"))

// Allows testing of annotation processor
testImplementation(libs.compile.testing)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.build.transforms;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import software.amazon.smithy.build.ProjectionTransformer;
import software.amazon.smithy.build.TransformContext;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.transform.ModelTransformer;
import software.amazon.smithy.rulesengine.aws.s3.S3TreeRewriter;
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
import software.amazon.smithy.rulesengine.language.evaluation.TestEvaluator;
import software.amazon.smithy.rulesengine.logic.bdd.CostOptimization;
import software.amazon.smithy.rulesengine.logic.bdd.SiftingOptimization;
import software.amazon.smithy.rulesengine.logic.cfg.Cfg;
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
import software.amazon.smithy.rulesengine.traits.EndpointTestCase;
import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait;

/**
* Compiles a Binary Decision Diagram (BDD) from a service's {@code @endpointRuleSet}
* trait and attaches the compiled {@code @endpointBdd} trait to the service shape.
*/
public final class CompileBddFromEndpointRuleSet implements ProjectionTransformer {
private static final ShapeId S3_SERVICE_ID = ShapeId.from("com.amazonaws.s3#AmazonS3");

@Override
public String getName() {
return "compileBddFromEndpointRuleSet";
}

@Override
public Model transform(TransformContext transformContext) {
Model model = transformContext.getModel();
Collection<Shape> shapes = new HashSet<>();
Set<ServiceShape> serviceShapes = model.getServiceShapes();
for (ServiceShape serviceShape : serviceShapes) {
if (serviceShape.hasTrait(EndpointRuleSetTrait.ID)) {
EndpointRuleSetTrait endpointRuleSetTrait = serviceShape.expectTrait(EndpointRuleSetTrait.class);
EndpointRuleSet rules = endpointRuleSetTrait.getEndpointRuleSet();
// S3 has special tree transformations that dramatically improve the BDD compiled result
// both in size and performance
if (serviceShape.getId().equals(S3_SERVICE_ID)) {
rules = transformRulesetForS3(rules, serviceShape.expectTrait(EndpointTestsTrait.class));
}
EndpointBddTrait bdd = compileBdd(rules);
shapes.add(serviceShape.toBuilder().addTrait(bdd).build());
}
}
return ModelTransformer.create().replaceShapes(model, shapes);
}

private EndpointRuleSet transformRulesetForS3(EndpointRuleSet rules, EndpointTestsTrait testsTrait) {
EndpointRuleSet transformedRules = S3TreeRewriter.transform(rules);
// Verify transforms preserve semantics by running all test cases
for (EndpointTestCase testCase : testsTrait.getTestCases()) {
TestEvaluator.evaluate(transformedRules, testCase);
}
return transformedRules;
}

private EndpointBddTrait compileBdd(EndpointRuleSet rules) {
// Create the CFG to start BDD compilation process.
Cfg cfg = Cfg.from(rules);
EndpointBddTrait unoptimizedTrait = EndpointBddTrait.from(cfg);

// Sift the BDD to shorten paths and reduce the BDD size.
EndpointBddTrait siftedTrait = SiftingOptimization.builder().cfg(cfg).build().apply(unoptimizedTrait);

// "cost optimize" the BDD to ensure cheap conditions come first with up to 10% size impact.
EndpointBddTrait costOptimizedTrait = CostOptimization.builder().cfg(cfg).build().apply(siftedTrait);

// Remove unreferenced conditions. This is destructive and further optimizations cannot be applied after this.
return costOptimizedTrait.removeUnreferencedConditions();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
software.amazon.smithy.build.transforms.Apply
software.amazon.smithy.build.transforms.ChangeStringEnumsToEnumShapes
software.amazon.smithy.build.transforms.ChangeTypes
software.amazon.smithy.build.transforms.CompileBddFromEndpointRuleSet
software.amazon.smithy.build.transforms.ExcludeMetadata
software.amazon.smithy.build.transforms.ExcludeShapesBySelector
software.amazon.smithy.build.transforms.ExcludeShapesByTag
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.build.transforms;

import static org.junit.Assert.assertTrue;

import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.build.TransformContext;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;

public class CompileBddFromEndpointRuleSetTest {

@Test
public void compilesAndAttachesBddTrait() throws Exception {
ShapeId serviceId = ShapeId.from("smithy.example#ExampleService");
Model model = Model.assembler()
.discoverModels()
.addImport(Paths.get(getClass().getResource("compile-bdd-from-endpoint-ruleset.smithy").toURI()))
.assemble()
.unwrap();
TransformContext context = TransformContext.builder()
.model(model)
.build();
Model result = new CompileBddFromEndpointRuleSet().transform(context);
Shape serviceShape = result.expectShape(serviceId);
assertTrue(serviceShape.hasTrait(EndpointBddTrait.ID));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
$version: "2.0"

namespace smithy.example

use smithy.rules#clientContextParams
use smithy.rules#endpointRuleSet
use smithy.rules#endpointTests

@clientContextParams(
Region: {type: "string", documentation: "docs"}
)
@endpointRuleSet({
"version": "1.1"
"parameters": {
"Region": {
"required": true
"type": "String"
"documentation": "docs"
}
}
"rules": [
{
"conditions": []
"documentation": "base rule"
"endpoint": {
"url": "https://{Region}.amazonaws.com"
"properties": {}
"headers": {}
}
"type": "endpoint"
}
]
})
@endpointTests({
"version": "1.0"
"testCases": [
{
"documentation": "example endpoint test"
"expect": {
"endpoint": {
"url": "https://example-region.amazonaws.com"
}
}
"params": {
Region: "example-region"
}
}
]
})
service ExampleService {}
Loading