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
143 changes: 143 additions & 0 deletions src/compiler/compile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
#include <utility> // std::move, std::pair
#include <vector> // std::vector

#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS)
#include <cstdio> // stderr
#include <functional> // std::function
#include <print> // std::println
#endif

#include "compile_helpers.h"
#include "postprocess.h"

Expand Down Expand Up @@ -148,6 +154,138 @@ auto schema_frame_populate_target_types(
}
}

auto is_evaluation_mark_instruction(
const sourcemeta::blaze::InstructionIndex type) -> bool {
using sourcemeta::blaze::InstructionIndex;
switch (type) {
case InstructionIndex::AssertionArrayPrefixEvaluate:
case InstructionIndex::AssertionPropertyTypeEvaluate:
case InstructionIndex::AssertionPropertyTypeStrictEvaluate:
case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate:
case InstructionIndex::Evaluate:
case InstructionIndex::LogicalNotEvaluate:
case InstructionIndex::LoopPropertiesUnevaluated:
case InstructionIndex::LoopPropertiesUnevaluatedExcept:
case InstructionIndex::LoopPropertiesEvaluate:
case InstructionIndex::LoopPropertiesTypeEvaluate:
case InstructionIndex::LoopPropertiesTypeStrictEvaluate:
case InstructionIndex::LoopPropertiesTypeStrictAnyEvaluate:
case InstructionIndex::LoopItemsUnevaluated:
case InstructionIndex::ControlEvaluate:
return true;
default:
return false;
}
}

auto contains_evaluation_marks(const sourcemeta::blaze::Instructions &steps,
const std::vector<bool> &target_marks,
const bool dynamic_marks) -> bool {
using sourcemeta::blaze::InstructionIndex;
for (const auto &step : steps) {
if (is_evaluation_mark_instruction(step.type)) {
return true;
}

if (step.type == InstructionIndex::ControlJump &&
target_marks[std::get<sourcemeta::blaze::ValueUnsignedInteger>(
step.value)]) {
return true;
}

if (step.type == InstructionIndex::ControlDynamicAnchorJump &&
dynamic_marks) {
return true;
}

if (contains_evaluation_marks(step.children, target_marks, dynamic_marks)) {
return true;
}
}

return false;
}

auto flag_evaluation_marks(sourcemeta::blaze::Instructions &steps,
const std::vector<bool> &target_marks,
const bool dynamic_marks) -> bool {
using sourcemeta::blaze::InstructionIndex;
bool result{false};
for (auto &step : steps) {
step.track =
is_evaluation_mark_instruction(step.type) ||
(step.type == InstructionIndex::ControlJump &&
target_marks[std::get<sourcemeta::blaze::ValueUnsignedInteger>(
step.value)]) ||
(step.type == InstructionIndex::ControlDynamicAnchorJump &&
dynamic_marks);
if (flag_evaluation_marks(step.children, target_marks, dynamic_marks)) {
step.track = true;
}

result = result || step.track;
}

return result;
}

// Determine which instructions can produce or consume evaluation marks
// somewhere in their subtree, including through static and dynamic jumps,
// so that the evaluator only maintains the evaluate path on the spines that
// lead to those instructions. Note that the evaluate path of every mark and
// of every mark consumer is preserved exactly: all of their ancestors are
// flagged, and only instructions that no mark or consumer can ever observe
// stop pushing
auto flag_evaluation_tracking(
std::vector<sourcemeta::blaze::Instructions> &targets,
const std::vector<std::pair<std::size_t, std::size_t>> &labels) -> void {
std::vector<bool> target_marks(targets.size(), false);
bool dynamic_marks{false};
bool changed{true};
while (changed) {
changed = false;
for (std::size_t index = 0; index < targets.size(); index++) {
if (!target_marks[index] &&
contains_evaluation_marks(targets[index], target_marks,
dynamic_marks)) {
target_marks[index] = true;
changed = true;
}
}

if (!dynamic_marks &&
std::ranges::any_of(labels, [&target_marks](const auto &label) {
return target_marks[label.second];
})) {
dynamic_marks = true;
changed = true;
}
}

for (auto &target : targets) {
flag_evaluation_marks(target, target_marks, dynamic_marks);
}

#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS)
std::size_t flagged{0};
std::size_t total{0};
// NOLINTNEXTLINE(misc-no-recursion)
const std::function<void(const sourcemeta::blaze::Instructions &)> count{
[&flagged, &total, &count](const sourcemeta::blaze::Instructions &steps) {
for (const auto &step : steps) {
total += 1;
flagged += step.track ? 1 : 0;
count(step.children);
}
}};
for (const auto &target : targets) {
count(target);
}

std::println(stderr, "TRACK FLAGS: {} / {}", flagged, total);
#endif
}

} // namespace

namespace sourcemeta::blaze {
Expand Down Expand Up @@ -421,6 +559,11 @@ auto compile(const sourcemeta::core::JSON &schema,
std::ranges::any_of(context.unevaluated, [](const auto &dependency) {
return dependency.first.ends_with("unevaluatedItems");
})};

if (track) {
flag_evaluation_tracking(compiled_targets, labels_map);
}

return {.dynamic = uses_dynamic_scopes,
.track = track,
.targets = std::move(compiled_targets),
Expand Down
75 changes: 69 additions & 6 deletions src/compiler/default_compiler_draft3.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@

#include <algorithm> // std::sort, std::ranges::any_of, std::ranges::all_of, std::find_if, std::ranges::none_of
#include <cassert> // assert
#include <map> // std::map
#include <set> // std::set
#include <utility> // std::move, std::to_underlying
#include <string> // std::string
#include <unordered_map> // std::unordered_map
#include <utility> // std::move, std::to_underlying

#include "compile_helpers.h"

Expand Down Expand Up @@ -104,10 +107,17 @@ compile_properties(const sourcemeta::blaze::Context &context,
// we prefer to evaluate smaller subschemas first, in the hope of failing
// earlier without spending a lot of time on other subschemas
if (context.tweaks.properties_reorder) {
std::ranges::sort(properties, [&context](const auto &left,
const auto &right) {
const auto left_size{recursive_template_size(left.second)};
const auto right_size{recursive_template_size(right.second)};
std::unordered_map<std::string, std::size_t> template_sizes;
template_sizes.reserve(properties.size());
for (const auto &property : properties) {
template_sizes.emplace(property.first,
recursive_template_size(property.second));
}

std::ranges::sort(properties, [&context, &template_sizes](
const auto &left, const auto &right) {
const auto left_size{template_sizes.at(left.first)};
const auto right_size{template_sizes.at(right.first)};
if (left_size == right_size) {
const auto left_direct_enumeration{
defines_direct_enumeration(left.second)};
Expand Down Expand Up @@ -548,10 +558,11 @@ auto compiler_draft3_applicator_properties_with_options(
ValueNamedIndexes indexes;
Instructions children;
std::size_t cursor = 0;
std::map<sourcemeta::core::JSON::String, std::size_t> child_positions;

for (auto &&[name, substeps] : compile_properties(
context, schema_context, relative_dynamic_context(), current)) {
indexes.emplace(name, cursor);
child_positions.emplace(name, cursor);

if (track_evaluation) {
substeps.push_back(make(
Expand All @@ -574,6 +585,17 @@ auto compiler_draft3_applicator_properties_with_options(
cursor += 1;
}

// Lay the lookup table out in schema declaration order, as instances
// commonly follow it, which makes scanning the table for each instance
// property terminate sooner on average
for (const auto &entry :
schema_context.schema.at(dynamic_context.keyword).as_object()) {
const auto match{child_positions.find(entry.first)};
if (match != child_positions.cend()) {
indexes.emplace(entry.first, match->second);
}
}

if (context.mode == Mode::FastValidation && !track_evaluation &&
!schema_context.schema.defines("patternProperties") &&
schema_context.schema.defines("additionalProperties") &&
Expand Down Expand Up @@ -892,6 +914,47 @@ auto compiler_draft3_applicator_properties_with_options(
for (auto &&step : substeps) {
children.push_back(std::move(step));
}
} else if (
context.mode == Mode::FastValidation &&
std::ranges::all_of(substeps, [&name](const auto &step) {
if (step.relative_instance_location.empty() ||
!step.relative_instance_location.at(0).is_property() ||
step.relative_instance_location.at(0).to_property() !=
name) {
return false;
}

if (step.relative_instance_location.size() > 1) {
return true;
}

// These instructions require a non-empty
// instance location of their own
switch (step.type) {
case InstructionIndex::AssertionPropertyType:
case InstructionIndex::AssertionPropertyTypeEvaluate:
case InstructionIndex::AssertionPropertyTypeStrict:
case InstructionIndex::AssertionPropertyTypeStrictEvaluate:
case InstructionIndex::AssertionPropertyTypeStrictAny:
case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate:
case InstructionIndex::ControlGroupWhenDefines:
return false;
default:
return true;
}
})) {
// When every step navigates into the property, we can resolve
// it once and evaluate the steps against its value directly
for (auto &step : substeps) {
step.relative_instance_location =
step.relative_instance_location.slice(1);
}

children.push_back(make(sourcemeta::blaze::InstructionIndex::
ControlGroupWhenDefinesResolved,
context, schema_context,
effective_dynamic_context,
make_property(name), std::move(substeps)));
} else {
children.push_back(make(sourcemeta::blaze::InstructionIndex::
ControlGroupWhenDefinesDirect,
Expand Down
Loading
Loading