Skip to content
Draft
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
6 changes: 4 additions & 2 deletions benchmark/e2e/runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
#include <sourcemeta/blaze/compiler.h>
#include <sourcemeta/blaze/evaluator.h>

// clang-format off
#define REGISTER_E2E_COMPILER(name, directory_name) \
static auto E2E_Compiler_##name(benchmark::State &state)->void { \
static auto E2E_Compiler_##name(benchmark::State &state) -> void { \
const std::filesystem::path directory{CURRENT_DIRECTORY \
"/e2e/" directory_name}; \
const auto schema{sourcemeta::core::read_json(directory / "schema.json")}; \
Expand All @@ -30,7 +31,7 @@
BENCHMARK(E2E_Compiler_##name)

#define REGISTER_E2E_EVALUATOR(name, directory_name) \
static auto E2E_Evaluator_##name(benchmark::State &state)->void { \
static auto E2E_Evaluator_##name(benchmark::State &state) -> void { \
const std::filesystem::path directory{CURRENT_DIRECTORY \
"/e2e/" directory_name}; \
const auto schema{sourcemeta::core::read_json(directory / "schema.json")}; \
Expand All @@ -56,6 +57,7 @@
} \
} \
BENCHMARK(E2E_Evaluator_##name)
// clang-format on

REGISTER_E2E_COMPILER(adaptivecard, "adaptivecard");
REGISTER_E2E_COMPILER(ansible_meta, "ansible-meta");
Expand Down
18 changes: 14 additions & 4 deletions src/compiler/compile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <utility> // std::move, std::pair

#include "compile_helpers.h"
#include "vocabulary_lookup.h"

namespace {

Expand All @@ -33,6 +34,12 @@ auto compile_subschema(const sourcemeta::blaze::Context &context,
}

Instructions steps;
// Reserve capacity to avoid reallocations - typical schemas have 3-10
// keywords
if (schema_context.schema.is_object()) {
steps.reserve(schema_context.schema.size());
}

for (const auto &entry : sourcemeta::core::SchemaKeywordIterator{
schema_context.schema, context.walker, context.resolver,
default_dialect}) {
Expand Down Expand Up @@ -285,11 +292,14 @@ auto compile(const sourcemeta::core::JSON &schema,
///////////////////////////////////////////////////////////////////

Instructions compiler_template;
// Reserve initial capacity for the template
compiler_template.reserve(32);

if (uses_dynamic_scopes &&
(schema_context.vocabularies.contains(
"https://json-schema.org/draft/2019-09/vocab/core") ||
schema_context.vocabularies.contains(
"https://json-schema.org/draft/2020-12/vocab/core"))) {
(has_vocabulary(schema_context.vocabularies,
"https://json-schema.org/draft/2019-09/vocab/core") ||
has_vocabulary(schema_context.vocabularies,
"https://json-schema.org/draft/2020-12/vocab/core"))) {
for (const auto &entry : context.frame.locations()) {
// We are only trying to find dynamic anchors
if (entry.second.type !=
Expand Down
22 changes: 15 additions & 7 deletions src/compiler/compile_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,30 @@
#include <utility> // std::declval, std::move
#include <variant> // std::visit

#include "vocabulary_lookup.h"

namespace sourcemeta::blaze {

// Static empty string for DynamicContext with no keyword
inline const std::string &empty_keyword() {
static const std::string value{""};
return value;
}

inline auto relative_dynamic_context() -> DynamicContext {
return {"", sourcemeta::core::empty_pointer, sourcemeta::core::empty_pointer,
false};
return {empty_keyword(), sourcemeta::core::empty_pointer,
sourcemeta::core::empty_pointer, false};
}

inline auto relative_dynamic_context(const DynamicContext &dynamic_context)
-> DynamicContext {
return {"", sourcemeta::core::empty_pointer, sourcemeta::core::empty_pointer,
dynamic_context.property_as_target};
return {empty_keyword(), sourcemeta::core::empty_pointer,
sourcemeta::core::empty_pointer, dynamic_context.property_as_target};
}

inline auto property_relative_dynamic_context() -> DynamicContext {
return {"", sourcemeta::core::empty_pointer, sourcemeta::core::empty_pointer,
true};
return {empty_keyword(), sourcemeta::core::empty_pointer,
sourcemeta::core::empty_pointer, true};
}

inline auto schema_resource_id(const std::vector<std::string> &resources,
Expand Down Expand Up @@ -222,7 +230,7 @@ inline auto find_adjacent(const Context &context,

if (std::any_of(vocabularies.cbegin(), vocabularies.cend(),
[&subschema_vocabularies](const auto &vocabulary) {
return subschema_vocabularies.contains(vocabulary);
return has_vocabulary(subschema_vocabularies, vocabulary);
}) &&
subschema.type() == type) {
result.emplace_back(subschema);
Expand Down
12 changes: 7 additions & 5 deletions src/compiler/default_compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "default_compiler_draft4.h"
#include "default_compiler_draft6.h"
#include "default_compiler_draft7.h"
#include "vocabulary_lookup.h"

#include <cassert> // assert
#include <string> // std::string
Expand Down Expand Up @@ -39,7 +40,8 @@ auto sourcemeta::blaze::default_schema_compiler(
"http://json-schema.org/draft-06/hyper-schema#",
"http://json-schema.org/draft-04/schema#",
"http://json-schema.org/draft-04/hyper-schema#"};
for (const auto &vocabulary : schema_context.vocabularies) {
for (const auto &vocabulary :
schema_context.vocabularies.all_vocabularies()) {
if (!SUPPORTED_VOCABULARIES.contains(vocabulary.first) &&
vocabulary.second) {
throw sourcemeta::core::SchemaVocabularyError(
Expand All @@ -50,22 +52,22 @@ auto sourcemeta::blaze::default_schema_compiler(
using namespace sourcemeta::blaze;

#define COMPILE(vocabulary, _keyword, handler) \
if (schema_context.vocabularies.contains(vocabulary) && \
if (has_vocabulary(schema_context.vocabularies, vocabulary) && \
dynamic_context.keyword == (_keyword)) { \
return internal::handler(context, schema_context, dynamic_context, \
current); \
}

#define COMPILE_ANY(vocabulary_1, vocabulary_2, _keyword, handler) \
if ((schema_context.vocabularies.contains(vocabulary_1) || \
schema_context.vocabularies.contains(vocabulary_2)) && \
if ((has_vocabulary(schema_context.vocabularies, vocabulary_1) || \
has_vocabulary(schema_context.vocabularies, vocabulary_2)) && \
dynamic_context.keyword == (_keyword)) { \
return internal::handler(context, schema_context, dynamic_context, \
current); \
}

#define STOP_IF_SIBLING_KEYWORD(vocabulary, _keyword) \
if (schema_context.vocabularies.contains(vocabulary) && \
if (has_vocabulary(schema_context.vocabularies, vocabulary) && \
schema_context.schema.is_object() && \
schema_context.schema.defines(_keyword)) { \
return {}; \
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/include/sourcemeta/blaze/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ struct SchemaContext {
/// disposal to implement a keyword
struct DynamicContext {
/// The schema keyword
const std::string keyword;
const std::string &keyword;
/// The schema base keyword path
const sourcemeta::core::Pointer &base_schema_location;
/// The base instance location that the keyword must be evaluated to
Expand Down
30 changes: 19 additions & 11 deletions src/compiler/unevaluated.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <sourcemeta/blaze/compiler.h>

#include "vocabulary_lookup.h"

namespace {
using namespace sourcemeta::core;
using sourcemeta::blaze::has_vocabulary;

auto find_adjacent_dependencies(
const JSON::String &current, const JSON &schema, const SchemaFrame &frame,
Expand All @@ -22,7 +25,8 @@ auto find_adjacent_dependencies(
continue;
} else if (keywords.contains(property.first)) {
// In 2019-09, `additionalItems` takes no effect without `items`
if (subschema_vocabularies.contains(
if (has_vocabulary(
subschema_vocabularies,
"https://json-schema.org/draft/2019-09/vocab/applicator") &&
property.first == "additionalItems" && !subschema.defines("items")) {
continue;
Expand Down Expand Up @@ -154,9 +158,11 @@ auto unevaluated(const JSON &schema, const SchemaFrame &frame,
const auto keyword_uri{frame.uri(entry.second, {pair.first})};
SchemaUnevaluatedEntry unevaluated;

if ((subschema_vocabularies.contains(
if ((has_vocabulary(
subschema_vocabularies,
"https://json-schema.org/draft/2020-12/vocab/unevaluated") &&
subschema_vocabularies.contains(
has_vocabulary(
subschema_vocabularies,
"https://json-schema.org/draft/2020-12/vocab/applicator")) &&
// NOLINTNEXTLINE(bugprone-branch-clone)
pair.first == "unevaluatedProperties") {
Expand All @@ -167,29 +173,31 @@ auto unevaluated(const JSON &schema, const SchemaFrame &frame,
entry.second, entry.second, true, unevaluated);
result.emplace(keyword_uri, std::move(unevaluated));
} else if (
(subschema_vocabularies.contains(
(has_vocabulary(
subschema_vocabularies,
"https://json-schema.org/draft/2020-12/vocab/unevaluated") &&
subschema_vocabularies.contains(
has_vocabulary(
subschema_vocabularies,
"https://json-schema.org/draft/2020-12/vocab/applicator")) &&
pair.first == "unevaluatedItems") {
find_adjacent_dependencies(
pair.first, schema, frame, walker, resolver,
{"prefixItems", "items", "contains", "unevaluatedItems"},
entry.second, entry.second, true, unevaluated);
result.emplace(keyword_uri, std::move(unevaluated));
} else if (subschema_vocabularies.contains(
"https://json-schema.org/draft/2019-09/vocab/"
"applicator") &&
} else if (has_vocabulary(subschema_vocabularies,
"https://json-schema.org/draft/2019-09/vocab/"
"applicator") &&
pair.first == "unevaluatedProperties") {
find_adjacent_dependencies(
pair.first, schema, frame, walker, resolver,
{"properties", "patternProperties", "additionalProperties",
"unevaluatedProperties"},
entry.second, entry.second, true, unevaluated);
result.emplace(keyword_uri, std::move(unevaluated));
} else if (subschema_vocabularies.contains(
"https://json-schema.org/draft/2019-09/vocab/"
"applicator") &&
} else if (has_vocabulary(subschema_vocabularies,
"https://json-schema.org/draft/2019-09/vocab/"
"applicator") &&
pair.first == "unevaluatedItems") {
find_adjacent_dependencies(
pair.first, schema, frame, walker, resolver,
Expand Down
20 changes: 20 additions & 0 deletions src/compiler/vocabulary_lookup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef SOURCEMETA_BLAZE_COMPILER_VOCABULARY_LOOKUP_H_
#define SOURCEMETA_BLAZE_COMPILER_VOCABULARY_LOOKUP_H_

#include <sourcemeta/core/jsonschema.h>

#include <string_view> // std::string_view

namespace sourcemeta::blaze {

// Optimized vocabulary lookup using bitwise operations
// This directly uses the Vocabularies::contains() method which performs
// O(1) bitwise AND operations for known vocabularies
inline auto has_vocabulary(const sourcemeta::core::Vocabularies &vocabularies,
std::string_view uri) -> bool {
return vocabularies.contains(uri);
}

} // namespace sourcemeta::blaze

#endif
11 changes: 7 additions & 4 deletions src/evaluator/evaluator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,18 @@ auto Evaluator::validate(const Template &schema,
assert(this->instance_location.empty());
assert(this->resources.empty());
this->labels.clear();
// Reserve capacity to avoid rehashing during evaluation
this->labels.reserve(16);

if (schema.track && schema.dynamic) {
if (schema.track && schema.dynamic) [[unlikely]] {
this->evaluated_.clear();
return complete::evaluate(instance, *this, schema, nullptr);
} else if (schema.track) {
} else if (schema.track) [[unlikely]] {
this->evaluated_.clear();
return track::evaluate(instance, *this, schema);
} else if (schema.dynamic) {
} else if (schema.dynamic) [[unlikely]] {
return dynamic::evaluate(instance, *this, schema);
} else {
} else [[likely]] {
return fast::evaluate(instance, *this, schema);
}
}
Expand All @@ -87,6 +89,7 @@ auto Evaluator::validate(const Template &schema,
assert(this->instance_location.empty());
assert(this->resources.empty());
this->labels.clear();
this->labels.reserve(16);
this->evaluated_.clear();

return complete::evaluate(instance, *this, schema, callback);
Expand Down
Loading