Skip to content

Commit

Permalink
Implement skipping for any field with known size.
Browse files Browse the repository at this point in the history
This patch adds `skip` support for fields with `&size` attribute or of
builtin type with known size. If a unit has a known size and it is
specified in a `&size` attribute this also allows to skip over unit
fields.

Closes #1640.
  • Loading branch information
bbannier committed Jan 12, 2024
1 parent c5f1e19 commit 8731132
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 21 deletions.
11 changes: 9 additions & 2 deletions spicy/toolchain/src/compiler/codegen/grammar-builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,20 @@ struct Visitor : public hilti::visitor::PreOrder<Production, Visitor> {
// Skipping not supported
}

else if ( const auto& size = AttributeSet::find(n.attributes(), "&size") ||
n.parseType().isA<type::Address>() || n.parseType().isA<type::Bitfield>() ||
n.parseType().isA<type::Real>() || n.parseType().isA<type::SignedInteger>() ||
n.parseType().isA<type::UnsignedInteger>() )

skip = production::Skip(cg->uniquer()->get(n.id()), NodeRef(p.node), {}, n.meta().location());

else if ( n.parseType().isA<type::Bytes>() ) {
// Bytes with fixed size already handled above.
auto eod_attr = AttributeSet::find(n.attributes(), "&eod");
auto size_attr = AttributeSet::find(n.attributes(), "&size");
auto until_attr = AttributeSet::find(n.attributes(), "&until");
auto until_including_attr = AttributeSet::find(n.attributes(), "&until-including");

if ( eod_attr || size_attr || until_attr || until_including_attr )
if ( eod_attr || until_attr || until_including_attr )
skip = production::Skip(cg->uniquer()->get(n.id()), NodeRef(p.node), {}, n.meta().location());
}

Expand Down
66 changes: 51 additions & 15 deletions spicy/toolchain/src/compiler/codegen/parser-builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1699,34 +1699,70 @@ struct ProductionVisitor
}

void operator()(const production::Skip& p) {
auto consumeFixedSize = [&](const Expression& size) {
assert(size.type().template isA<type::UnsignedInteger>());

auto n = builder()->addTmp("skip", size);
auto loop = builder()->addWhile(builder::greater(n, builder::integer(0U)));
pushBuilder(loop, [&]() {
pb->waitForInput(builder::integer(1U), "not enough bytes for skipping", p.location());
auto consume = builder()->addTmp("consume", builder::min(builder::size(state().cur), size));
pb->advanceInput(consume);
builder()->addAssign(n, builder::difference(n, consume));
builder()->addDebugMsg("spicy-verbose", "- skipped %u bytes (%u left to skip)", {consume, n});
});
};

auto fixedSize = [&](const production::Skip& p) {
if ( const auto& size = AttributeSet::find(p.field().attributes(), "&size") )
return std::optional(size.value().valueAsExpression()->get());

else if ( auto t = p.field().parseType().tryAs<type::SignedInteger>() )
return std::optional(builder::integer(static_cast<uint64_t>(t->width() / 8)));

else if ( auto t = p.field().parseType().tryAs<type::UnsignedInteger>() )
return std::optional(builder::integer(static_cast<uint64_t>(t->width() / 8)));

else if ( auto t = p.field().parseType().tryAs<type::Bitfield>() )
return std::optional(builder::integer(static_cast<uint64_t>(t->width() / 8)));

else if ( p.field().parseType().isA<type::Address>() ) {
if ( AttributeSet::find(p.field().attributes(), "&ipv4") )
return std::optional(builder::integer(4U));
else if ( AttributeSet::find(p.field().attributes(), "&ipv6") )
return std::optional(builder::integer(16U));
else
hilti::rt::cannot_be_reached();
}

else if ( auto t = p.field().parseType().tryAs<type::Real>() ) {
auto type = *p.field().attributes()->find("&type")->valueAsExpression();
return std::optional(
builder::ternary(builder::equal(type, builder::id("spicy::RealType::IEEE754_Single")),
builder::integer(4U), builder::integer(8U)));
}

return std::optional<Expression>();
};

if ( auto c = p.field().condition() )
pushBuilder(builder()->addIf(*c));

if ( const auto& ctor = p.ctor() ) {
pb->skipLiteral(*ctor);
}

else if ( const auto& size = fixedSize(p) )
consumeFixedSize(*size);

else if ( p.field().parseType().isA<type::Bytes>() ) {
// Bytes with fixed size already handled above.
auto eod_attr = AttributeSet::find(p.field().attributes(), "&eod");
auto size_attr = AttributeSet::find(p.field().attributes(), "&size");
auto until_attr = AttributeSet::find(p.field().attributes(), "&until");
if ( ! until_attr )
until_attr = AttributeSet::find(p.field().attributes(), "&until-including");

if ( size_attr ) {
auto n = builder()->addTmp("skip", *size_attr->valueAsExpression());
auto loop = builder()->addWhile(builder::greater(n, builder::integer(0U)));
pushBuilder(loop, [&]() {
pb->waitForInput(builder::integer(1U), "not enough bytes for skipping", p.location());
auto consume = builder()->addTmp("consume", builder::min(builder::size(state().cur),
*size_attr->valueAsExpression()));
pb->advanceInput(consume);
builder()->addAssign(n, builder::difference(n, consume));
builder()->addDebugMsg("spicy-verbose", "- skipped %u bytes (%u left to skip)", {consume, n});
});
}

else if ( eod_attr ) {
if ( eod_attr ) {
builder()->addDebugMsg("spicy-verbose", "- skipping to eod");
auto loop = builder()->addWhile(pb->waitForInputOrEod());
pushBuilder(loop, [&]() { pb->advanceInput(builder::size(state().cur)); });
Expand Down
15 changes: 15 additions & 0 deletions tests/Baseline/spicy.types.unit.skip-field/all-types-skips.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
# Begin parsing production: Skip: _anon -> skip: _anon
# Begin parsing production: Skip: _anon_2 -> skip: _anon_2
# Begin parsing production: Skip: _anon_3 -> skip: _anon_3
# Begin parsing production: Skip: _anon_4 -> skip: _anon_4
# Begin parsing production: Skip: _anon_5 -> skip: _anon_5
# Begin parsing production: Skip: _anon_6 -> skip: _anon_6
# Begin parsing production: Skip: _anon_7 -> skip: _anon_7
# Begin parsing production: Skip: _anon_8 -> skip: _anon_8
# Begin parsing production: Skip: _anon_9 -> skip: _anon_9
# Begin parsing production: Skip: _anon_10 -> skip: _anon_10
# Begin parsing production: Skip: _anon_11 -> skip: _anon_11
# Begin parsing production: Skip: _anon_12 -> skip: _anon_12
# Begin parsing production: Skip: _anon_13 -> skip: _anon_13
# Begin parsing production: Skip: anon -> skip: anon
4 changes: 2 additions & 2 deletions tests/Baseline/spicy.types.unit.skip-field/output
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
skip b
skip eos
[$foo=[$a=1, $c=4, $f=7], $x=10]
[$foo=[$f=7], $x=10]
skip b
skip eos
[$foo=[$a=1, $c=4, $f=7], $x=10]
[$foo=[$f=7], $x=10]
2 changes: 2 additions & 0 deletions tests/Baseline/spicy.types.unit.skip-field/skips.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
# Begin parsing production: Skip: a_2 -> skip: a
# Begin parsing production: Skip: _anon_6 -> skip: _anon
# Begin parsing production: Skip: c_2 -> skip: c
# Begin parsing production: Skip: _anon_2_2 -> skip: _anon_2
# Begin parsing production: Skip: _anon_3_2 -> skip: _anon_3
# Begin parsing production: Skip: _anon_4_2 -> skip: _anon_4
Expand Down
32 changes: 30 additions & 2 deletions tests/spicy/types/unit/skip-field.spicy
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
# Ensure we're actually producing Skip productions for fields where we support it
# @TEST-EXEC: spicyc -p %INPUT | grep 'Begin parsing production: Skip' >skips.txt
# @TEST-EXEC: btest-diff skips.txt

# @TEST-EXEC: spicyc -p all-types.spicy | grep 'Begin parsing production: Skip' >all-types-skips.txt
# @TEST-EXEC: btest-diff all-types-skips.txt
#
# @TEST-DOC: Exercise `skip` fields.

Expand All @@ -19,9 +22,9 @@ public type Root = unit {
};

type Foo = unit {
a: uint8;
a: skip uint8;
: skip bytes &size=2 { print "skip b"; }
c: uint8;
c: skip int8;
: skip bytes &size=2 if ( True );
: skip bytes &size=2 if ( False );
f: uint8; # == 0x07
Expand All @@ -37,3 +40,28 @@ type Bar = unit {
};

on Foo::eod { print "skip eos"; }

# @TEST-START-FILE all-types.spicy
module all_types;

import spicy;

public type Foo = unit {
: skip addr &ipv4;
: skip addr &ipv6;
: skip int8;
: skip int16;
: skip int32;
: skip int64;
: skip uint8;
: skip uint16;
: skip uint32;
: skip uint64;
: skip bitfield(8) {};
: skip real &type=spicy::RealType::IEEE754_Single;
: skip real &type=spicy::RealType::IEEE754_Double;
: skip Bar &size=1;
};

type Bar = unit { x: uint8; };
# @TEST-END-FILE

0 comments on commit 8731132

Please sign in to comment.