Skip to content

Commit

Permalink
Support bitfield constants.
Browse files Browse the repository at this point in the history
One can now define bitfield "constants" for parsing by providing
integer expressions with fields:

    type Foo = unit {
      x: bitfield(8) {
        a: 0..3 = 2;
        b: 4..7;
        c: 7 = 1;
      };

This will first parse the bitfield as usual and then enforce that the
two bit ranges that are coming with expressions (i.e., `a` and `c`)
indeed containing the expected values. If they don't, that's a parse
error.

We also support using such bitfield constants for look-ahead parsing:

    type Foo = unit {
      x: uint8[];
      y: bitfield(8) {
        a: 0..3 = 4;
        b: 4..7;
      };
    };

This will parse uint8s until a value is discovered that has its bits
set as defined by the bitfield constant.

(We use the term "constant" loosely here: only the bits with values
are actually enforced to be constant, all others are parsed as usual.)

TODO:
    - Test use for synchronization.
    - Add documentation.

Closes #1467.
  • Loading branch information
rsmmr committed Aug 31, 2023
1 parent 197bb24 commit a3bd5e9
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 11 deletions.
3 changes: 2 additions & 1 deletion spicy/toolchain/src/ast/types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

#include "spicy/ast/types.h"

#include <hilti/ast/types/bitfield.h>
#include <hilti/ast/types/bytes.h>
#include <hilti/ast/types/integer.h>
#include <hilti/ast/types/regexp.h>

bool spicy::type::supportsLiterals(const hilti::Type& t) {
return t.isA<hilti::type::Bytes>() || t.isA<hilti::type::RegExp>() || t.isA<hilti::type::SignedInteger>() ||
t.isA<hilti::type::UnsignedInteger>();
t.isA<hilti::type::UnsignedInteger>() || t.isA<hilti::type::Bitfield>();
}
64 changes: 64 additions & 0 deletions spicy/toolchain/src/compiler/codegen/parsers/literals.cc
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,70 @@ struct Visitor : public hilti::visitor::PreOrder<std::optional<Expression>, Visi
result_t operator()(const hilti::ctor::SignedInteger& c) {
return parseInteger(c.type(), builder::expression(c), c.meta());
}

result_t operator()(const hilti::ctor::Bitfield& c) {
switch ( state().literal_mode ) {
case LiteralMode::Default:
case LiteralMode::Skip: {
auto [have_lah, no_lah] = builder()->addIfElse(state().lahead);

pushBuilder(have_lah);

pushBuilder(builder()->addIf(builder::unequal(state().lahead, builder::integer(production.tokenID()))));
pb->parseError("unexpected token to consume", c.meta());
popBuilder();

// Need to reparse the value to assign it to our destination.
auto value = pb->parseType(c.btype(), production.meta(), {});
builder()->addAssign(destination(c.btype()), value);

pb->consumeLookAhead();
popBuilder();

pushBuilder(no_lah);
auto old_cur = builder()->addTmp("ocur", state().cur);

value = pb->parseType(c.btype(), production.meta(), {});

// Check that the bit values match what we expect.
for ( const auto& b : c.bits() ) {
auto error = builder()->addIf(builder::unequal(builder::member(value, b.id()), b.expression()));
pushBuilder(error);
builder()->addAssign(state().cur, old_cur);
pb->parseError(fmt("unexpected value for bitfield element '%s'", b.id()), c.meta());
popBuilder();
}

if ( state().literal_mode != LiteralMode::Skip )
builder()->addAssign(destination(c.btype()), value);

popBuilder();

return value;
}

case LiteralMode::Search: // Handled in `parseLiteral`.
case LiteralMode::Try: {
auto old_cur = builder()->addTmp("ocur", state().cur);
auto bf = builder()->addTmp("bf", c.btype());
pb->parseTypeTry(c.btype(), production.meta(), bf);
auto new_cur = builder()->addTmp("ncur", state().cur);
builder()->addAssign(state().cur, old_cur);

// Check that the bit values match what we expect.
for ( const auto& b : c.bits() ) {
auto error = builder()->addIf(builder::unequal(builder::member(bf, b.id()), b.expression()));
pushBuilder(error);
builder()->addAssign(new_cur, old_cur); // reset to old position
popBuilder();
}

return builder::begin(new_cur);
}
}

hilti::util::cannot_be_reached();
}
};

} // namespace
Expand Down
6 changes: 3 additions & 3 deletions spicy/toolchain/src/compiler/codegen/parsers/types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct Visitor : public hilti::visitor::PreOrder<Expression, Visitor> {
else {
auto has_data = pb->waitForInputOrEod(builder::integer(len));

auto result = builder()->addTmp("result", type::Result(t));
auto result = dst ? *dst : builder()->addTmp("result", type::Result(t));

auto true_ = builder()->addIf(has_data);
pushBuilder(true_);
Expand Down Expand Up @@ -303,7 +303,7 @@ struct Visitor : public hilti::visitor::PreOrder<Expression, Visitor> {

Expression ParserBuilder::_parseType(const Type& t, const production::Meta& meta, const std::optional<Expression>& dst,
bool is_try) {
assert(! is_try || (t.isA<type::SignedInteger>() || t.isA<type::UnsignedInteger>()));
assert(! is_try || (t.isA<type::SignedInteger>() || t.isA<type::UnsignedInteger>() || t.isA<type::Bitfield>()));

if ( auto e = Visitor(this, meta, dst, is_try).dispatch(t) )
return std::move(*e);
Expand All @@ -317,7 +317,7 @@ Expression ParserBuilder::parseType(const Type& t, const production::Meta& meta,

Expression ParserBuilder::parseTypeTry(const Type& t, const production::Meta& meta,
const std::optional<Expression>& dst) {
assert(t.isA<type::SignedInteger>() || t.isA<type::UnsignedInteger>());
assert(t.isA<type::SignedInteger>() || t.isA<type::UnsignedInteger>() || t.isA<type::Bitfield>());

return _parseType(t, meta, dst, /*is_try =*/true);
}
15 changes: 10 additions & 5 deletions spicy/toolchain/src/compiler/parser/parser.yy
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ static std::vector<hilti::DocString> _docs;
%type <hilti::Expression> expr tuple_elem tuple_expr member_expr ctor_expr expr_0 expr_1 expr_2 expr_3 expr_4 expr_5 expr_6 expr_7 expr_8 expr_9 expr_a expr_b expr_c expr_d expr_e expr_f expr_g
%type <std::vector<hilti::Expression>> opt_tuple_elems1 opt_tuple_elems2 exprs opt_exprs opt_unit_field_args opt_unit_field_sinks
%type <std::optional<hilti::Statement>> opt_else_block
%type <std::optional<hilti::Expression>> opt_init_expression opt_unit_field_condition unit_field_repeat opt_unit_field_repeat opt_unit_switch_expr
%type <std::optional<hilti::Expression>> opt_init_expression opt_unit_field_condition unit_field_repeat opt_unit_field_repeat opt_unit_switch_expr opt_bitfield_bits_value
%type <hilti::type::function::Parameter> func_param
%type <hilti::declaration::parameter::Kind> opt_func_param_kind
%type <hilti::type::function::Result> func_result opt_func_result
Expand Down Expand Up @@ -702,10 +702,15 @@ bitfield_bits
| bitfield_bits_spec { $$ = std::vector<spicy::type::bitfield::Bits>(); $$.push_back(std::move($1)); }

bitfield_bits_spec
: local_id ':' CUINTEGER DOTDOT CUINTEGER opt_attributes ';'
{ $$ = spicy::type::bitfield::Bits(std::move($1), $3, $5, _field_width, std::move($6), __loc__); }
| local_id ':' CUINTEGER opt_attributes ';'
{ $$ = spicy::type::bitfield::Bits(std::move($1), $3, $3, _field_width, std::move($4), __loc__); }
: local_id ':' CUINTEGER DOTDOT CUINTEGER opt_bitfield_bits_value opt_attributes ';'
{ $$ = spicy::type::bitfield::Bits(std::move($1), $3, $5, _field_width, std::move($7), std::move($6), __loc__); }
| local_id ':' CUINTEGER opt_bitfield_bits_value opt_attributes ';'
{ $$ = spicy::type::bitfield::Bits(std::move($1), $3, $3, _field_width, std::move($5), std::move($4), __loc__); }

opt_bitfield_bits_value
: '=' expr { $$ = std::move($2); }
| /* empty */ { $$ = {}; }
;

/* --- Begin of Spicy units --- */

Expand Down
10 changes: 10 additions & 0 deletions spicy/toolchain/src/compiler/visitors/resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ struct Visitor : public hilti::visitor::PreOrder<void, Visitor> {
if ( ! type::isResolved(t) )
return;

if ( auto bf = t->tryAs<type::Bitfield>() ) {
// If a bitfield type comes with values for at least one of its
// items, it's actually a bitfield ctor. Replace the type with the
// ctor then.
if ( auto ctor = bf->ctorValue() ) {
replaceField(&p, resolveField(u, *ctor));
return;
}
}

replaceField(&p, resolveField(u, *t));
}

Expand Down
2 changes: 2 additions & 0 deletions tests/Baseline/spicy.types.bitfield.ctor-look-ahead/output
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
[$x=[1, 2, 3], $y=(4, 8), $z=5]
3 changes: 3 additions & 0 deletions tests/Baseline/spicy.types.bitfield.ctor/output
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
(2, 8, 1)
[error] terminating with uncaught exception of type spicy::rt::ParseError: unexpected value for bitfield element 'a' (<...>/ctor.spicy:10:6-14:4)
4 changes: 2 additions & 2 deletions tests/Baseline/spicy.types.bitfield.width-fail/output
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
[error] <...>/width-fail.spicy:9:8-12:6: upper limit is beyond the width of the bitfield
[error] <...>/width-fail.spicy:9:8-12:6: lower limit needs to be lower than upper limit
[error] <...>/width-fail.spicy:10:9: upper limit is beyond the width of the bitfield
[error] <...>/width-fail.spicy:11:9: lower limit needs to be lower than upper limit
[error] spicyc: aborting after errors
17 changes: 17 additions & 0 deletions tests/spicy/types/bitfield/ctor-look-ahead.spicy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# @TEST-EXEC: printf '\001\002\003\204\005' | spicy-driver %INPUT >>output
# @TEST-EXEC: btest-diff output
#
# @TEST-DOC: Check look-ahead vector parsing with bitfield constant as terminator.

module Test;

public type Foo = unit {
x: uint8[];
y: bitfield(8) {
a: 0..3 = 4;
b: 4..7;
};
z: uint8;

on %done { print self; }
};
17 changes: 17 additions & 0 deletions tests/spicy/types/bitfield/ctor.spicy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# @TEST-EXEC: printf '\x82' | spicy-driver %INPUT >>output 2>&1
# @TEST-EXEC-FAIL: printf '\200' | spicy-driver %INPUT >>output 2>&1
# @TEST-EXEC: btest-diff output
#
# @TEST-DOC: Check bitfield constant parsing.

module Test;

public type Foo = unit {
x: bitfield(8) {
a: 0..3 = 2;
b: 4..7;
c: 7 = 1;
};

on %done { print self.x; }
};

0 comments on commit a3bd5e9

Please sign in to comment.