Skip to content

Commit 797b4e1

Browse files
paolopasgrafikrobot
authored andcommitted
util/order.jam (and engine/mod_order.cpp) review
+ restored Jam add-pair method, for better error control (Jam does a syntax check for arguments at call site with relative error message emission) and better preconditions checkings too: avoid null arguments, avoid duplicate constraints (which led to edge duplication in final graph,) and avoid self referencing (which led to cycles in final graph.) The old add-pair was even broken, since it spelled .constraits instead of .constraints + removed stale order method implementation, fully replaced by native one + removed all other stale methods, no more useful/functional mod_order.cpp rewritten + to implement the topological sort algorithm using std::vector instead of mem.h + also removed jam_strings.h include + prepared for future warning emission on "cyclic dependency" + no more need for native add_pair + native add-pair for backward compatibility fixes #593
1 parent a034fe4 commit 797b4e1

2 files changed

Lines changed: 72 additions & 199 deletions

File tree

src/engine/mod_order.cpp

Lines changed: 61 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
/* Copyright 2004. Vladimir Prus
2+
* Copyright 2026 Paolo Pastori
23
* Distributed under the Boost Software License, Version 1.0.
34
* (See accompanying file LICENSE.txt or copy at
45
* https://www.bfgroup.xyz/b2/LICENSE.txt)
56
*/
67

78
#include "frames.h"
89
#include "lists.h"
9-
#include "mem.h"
1010
#include "native.h"
1111
#include "object.h"
12-
#include "jam_strings.h"
1312
#include "variable.h"
1413

14+
#include <utility>
15+
#include <vector>
1516

16-
/* Use quite klugy approach: when we add order dependency from 'a' to 'b', just
17-
* append 'b' to of value of variable 'a'.
17+
18+
// vertex type, NOTE: limit the max number of vertices in the graph
19+
using node_typ = uint16_t;
20+
using node_vec = std::vector<node_typ>;
21+
using vec_graph = std::vector<node_vec>;
22+
23+
enum node_state { TO_VISIT, VISITING, VISITED };
24+
using state_vec = std::vector<char>;
25+
26+
27+
/* Use quite klugy approach: when we add order dependency from 'a' to 'b',
28+
* just append 'b' to of value of variable 'a'. NOTE: This is still here
29+
* only for backward compatibility reasons since latest order.jam use a
30+
* normal class method rule instead of this.
1831
*/
1932
LIST * add_pair( FRAME * frame, int32_t flags )
2033
{
@@ -30,7 +43,7 @@ LIST * add_pair( FRAME * frame, int32_t flags )
3043
/* Given a list and a value, returns position of that value in the list, or -1
3144
* if not found.
3245
*/
33-
int32_t list_index( LIST * list, OBJECT * value )
46+
static int32_t list_index( LIST * list, OBJECT * value )
3447
{
3548
int32_t result = 0;
3649
LISTITER iter = list_begin( list );
@@ -41,114 +54,74 @@ int32_t list_index( LIST * list, OBJECT * value )
4154
return -1;
4255
}
4356

44-
enum colors { white, gray, black };
45-
46-
47-
/* Main routine for topological sort. Calls itself recursively on all adjacent
48-
* vertices which were not yet visited. After that, 'current_vertex' is added to
49-
* '*result_ptr'.
57+
/* Routine for depth first traversal. Calls itself recursively on all adjacent
58+
* vertices which were not yet visited. After that, 'current_vertex' is added
59+
* to 'result'.
5060
*/
51-
void do_ts( int32_t * * graph, int32_t current_vertex, int32_t * colors, int32_t * * result_ptr
52-
)
61+
static void do_df( FRAME * frame, const vec_graph & graph,
62+
int32_t current_vertex, state_vec & state, node_vec & result )
5363
{
54-
int32_t i;
55-
56-
colors[ current_vertex ] = gray;
57-
for ( i = 0; graph[ current_vertex ][ i ] != -1; ++i )
64+
state[ current_vertex ] = VISITING;
65+
for ( auto adjacent_vertex : graph[ current_vertex ] )
5866
{
59-
int32_t adjacent_vertex = graph[ current_vertex ][ i ];
60-
if ( colors[ adjacent_vertex ] == white )
61-
do_ts( graph, adjacent_vertex, colors, result_ptr );
62-
/* The vertex is either black, in which case we do not have to do
63-
* anything, or gray, in which case we have a loop. If we have a loop,
64-
* it is not clear what useful diagnostic we can emit, so we emit
65-
* nothing.
66-
*/
67+
if ( state[ adjacent_vertex ] == TO_VISIT )
68+
do_df( frame, graph, adjacent_vertex, state, result );
69+
// TODO
70+
//if ( state[ adjacent_vertex ] == VISITING )
71+
// emit warning "Cyclic order dependency on 'x' and 'y'."
6772
}
68-
colors[ current_vertex ] = black;
69-
**result_ptr = current_vertex;
70-
( *result_ptr )++;
73+
state[ current_vertex ] = VISITED;
74+
result.push_back( static_cast<node_typ>( current_vertex ) );
7175
}
7276

73-
74-
static void topological_sort( int32_t * * graph, int32_t num_vertices, int32_t * result )
77+
static void topological_sort( FRAME * frame, const vec_graph & graph,
78+
int32_t size, node_vec & result )
7579
{
76-
int32_t i;
77-
int32_t * colors = ( int32_t * )BJAM_CALLOC( num_vertices, sizeof( int32_t ) );
78-
for ( i = 0; i < num_vertices; ++i )
79-
colors[ i ] = white;
80-
81-
for ( i = num_vertices - 1; i >= 0; --i )
82-
if ( colors[ i ] == white )
83-
do_ts( graph, i, colors, &result );
84-
85-
BJAM_FREE( colors );
80+
state_vec state(size, TO_VISIT);
81+
for ( int32_t i = size - 1; i >= 0; --i )
82+
if ( state[ i ] == TO_VISIT )
83+
do_df( frame, graph, i, state, result );
8684
}
8785

8886

8987
LIST * order( FRAME * frame, int32_t flags )
9088
{
91-
LIST * arg = lol_get( frame->args, 0 );
92-
LIST * result = L0;
93-
int32_t src;
94-
LISTITER iter = list_begin( arg );
95-
LISTITER const end = list_end( arg );
96-
97-
/* We need to create a graph of order dependencies between the passed
98-
* objects. We assume there are no duplicates passed to 'add_pair'.
99-
*/
100-
int32_t length = list_length( arg );
101-
int32_t * * graph = ( int32_t * * )BJAM_CALLOC( length, sizeof( int32_t * ) );
102-
int32_t * order = ( int32_t * )BJAM_MALLOC( ( length + 1 ) * sizeof( int32_t ) );
103-
104-
for ( src = 0; iter != end; iter = list_next( iter ), ++src )
89+
b2::list_cref arg( lol_get( frame->args, 0 ) );
90+
int32_t length = arg.length();
91+
if (length == 0) return L0;
92+
93+
// Build dependency graph
94+
vec_graph graph;
95+
graph.reserve( length );
96+
for ( auto & obj : arg )
10597
{
106-
/* For all objects this one depends upon, add elements to 'graph'. */
107-
LIST * dependencies = var_get( frame->module, list_item( iter ) );
108-
int32_t index = 0;
109-
LISTITER dep_iter = list_begin( dependencies );
110-
LISTITER const dep_end = list_end( dependencies );
111-
112-
graph[ src ] = ( int32_t * )BJAM_CALLOC( list_length( dependencies ) + 1,
113-
sizeof( int32_t ) );
114-
for ( ; dep_iter != dep_end; dep_iter = list_next( dep_iter ) )
98+
b2::list_cref deps( var_get( frame->module, obj ) );
99+
node_vec depl;
100+
depl.reserve( deps.length() );
101+
for ( auto & dep : deps )
115102
{
116-
int32_t const dst = list_index( arg, list_item( dep_iter ) );
103+
int32_t dst = list_index( *arg, dep );
117104
if ( dst != -1 )
118-
graph[ src ][ index++ ] = dst;
105+
depl.push_back( static_cast<node_typ>( dst ) ) ;
119106
}
120-
graph[ src ][ index ] = -1;
107+
graph.push_back( std::move( depl ) );
121108
}
122109

123-
topological_sort( graph, length, order );
110+
node_vec order;
111+
order.reserve( length );
112+
topological_sort( frame, graph, length, order );
124113

125-
{
126-
int32_t index = length - 1;
127-
for ( ; index >= 0; --index )
128-
{
129-
int32_t i;
130-
LISTITER iter = list_begin( arg );
131-
for ( i = 0; i < order[ index ]; ++i, iter = list_next( iter ) );
132-
result = list_push_back( result, object_copy( list_item( iter ) ) );
133-
}
134-
}
114+
b2::list_ref result;
115+
for ( int32_t i = length - 1; i >= 0; --i )
116+
result.push_back( object_copy( arg[ order[ i ] ] ) );
135117

136-
/* Clean up */
137-
{
138-
int32_t i;
139-
for ( i = 0; i < length; ++i )
140-
BJAM_FREE( graph[ i ] );
141-
BJAM_FREE( graph );
142-
BJAM_FREE( order );
143-
}
144-
145-
return result;
118+
return result.release();
146119
}
147120

148121

149122
void init_order()
150123
{
151-
{
124+
{ // for backward compatibility, see #593
152125
char const * args[] = { "first", "second", 0 };
153126
declare_native_rule( "class@order", "add-pair", args, add_pair, 1 );
154127
}

src/util/order.jam

Lines changed: 11 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -27,126 +27,27 @@
2727

2828
class order
2929
{
30-
rule __init__ ( )
31-
{
32-
}
30+
rule __init__ ( ) { }
3331

3432
# Adds the constraint that 'first' should preceede 'second'.
3533
rule add-pair ( first second )
3634
{
37-
.constraits += $(first)--$(second) ;
38-
}
39-
NATIVE_RULE class@order : add-pair ;
40-
41-
# Given a list of objects, reorder them so that the constraints specified by
42-
# 'add-pair' are satisfied.
43-
#
44-
# The algorithm was adopted from an awk script by Nikita Youshchenko
45-
# (yoush at cs dot msu dot su)
46-
rule order ( objects * )
47-
{
48-
# The algorithm used is the same is standard transitive closure, except
49-
# that we're not keeping in-degree for all vertices, but rather removing
50-
# edges.
51-
local result ;
52-
if $(objects)
53-
{
54-
local constraints = [ eliminate-unused-constraits $(objects) ] ;
55-
56-
# Find some library that nobody depends upon and add it to the
57-
# 'result' array.
58-
local obj ;
59-
while $(objects)
60-
{
61-
local new_objects ;
62-
while $(objects)
63-
{
64-
obj = $(objects[1]) ;
65-
if [ has-no-dependents $(obj) : $(constraints) ]
66-
{
67-
# Emulate break ;
68-
new_objects += $(objects[2-]) ;
69-
objects = ;
70-
}
71-
else
72-
{
73-
new_objects += $(obj) ;
74-
obj = ;
75-
objects = $(objects[2-]) ;
76-
}
77-
}
78-
79-
if ! $(obj)
80-
{
81-
errors.error "Circular order dependencies" ;
82-
}
83-
# No problem with placing first.
84-
result += $(obj) ;
85-
# Remove all constraints where 'obj' comes first, since they are
86-
# already satisfied.
87-
constraints = [ remove-satisfied $(constraints) : $(obj) ] ;
88-
89-
# Add the remaining objects for further processing on the next
90-
# iteration
91-
objects = $(new_objects) ;
92-
}
93-
94-
}
95-
return $(result) ;
96-
}
97-
NATIVE_RULE class@order : order ;
98-
99-
# Eliminate constraints which mention objects not in 'objects'. In
100-
# graph-theory terms, this is finding a subgraph induced by ordered
101-
# vertices.
102-
rule eliminate-unused-constraits ( objects * )
103-
{
104-
local result ;
105-
for local c in $(.constraints)
35+
if $(first) && $(second)
10636
{
107-
local m = [ MATCH (.*)--(.*) : $(c) ] ;
108-
if $(m[1]) in $(objects) && $(m[2]) in $(objects)
37+
# avoid duplicate and self dependencies
38+
if ! $(second) in $($(first)) && $(second) != $(first)
10939
{
110-
result += $(c) ;
40+
$(first) += $(second) ;
11141
}
11242
}
113-
return $(result) ;
11443
}
11544

116-
# Returns true if there's no constraint in 'constaraints' where 'obj' comes
117-
# second.
118-
rule has-no-dependents ( obj : constraints * )
119-
{
120-
local failed ;
121-
while $(constraints) && ! $(failed)
122-
{
123-
local c = $(constraints[1]) ;
124-
local m = [ MATCH (.*)--(.*) : $(c) ] ;
125-
if $(m[2]) = $(obj)
126-
{
127-
failed = true ;
128-
}
129-
constraints = $(constraints[2-]) ;
130-
}
131-
if ! $(failed)
132-
{
133-
return true ;
134-
}
135-
}
45+
# Given a list of objects, reorder them so that the constraints
46+
# specified by 'add-pair' are satisfied.
47+
#
48+
# rule order ( objects * )
13649

137-
rule remove-satisfied ( constraints * : obj )
138-
{
139-
local result ;
140-
for local c in $(constraints)
141-
{
142-
local m = [ MATCH (.*)--(.*) : $(c) ] ;
143-
if $(m[1]) != $(obj)
144-
{
145-
result += $(c) ;
146-
}
147-
}
148-
return $(result) ;
149-
}
50+
NATIVE_RULE class@order : order ;
15051
}
15152

15253

@@ -167,7 +68,6 @@ rule __test__ ( )
16768
assert.result l1 l2 : $(c1).order l2 l1 ;
16869
assert.result l1 l2 l3 : $(c1).order l2 l3 l1 ;
16970

170-
# The output should be stable for unconstrained
171-
# elements.
71+
# The output should be stable for unconstrained elements.
17272
assert.result l4 l5 : $(c1).order l4 l5 ;
17373
}

0 commit comments

Comments
 (0)