Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* GRAPHITE2 LICENSING
3 Copyright 2010, SIL International
4 All rights reserved.
6 This library is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should also have received a copy of the GNU Lesser General Public
17 License along with this library in the file named "LICENSE".
18 If not, write to the Free Software Foundation, 51 Franklin Street,
19 Suite 500, Boston, MA 02110-1335, USA or visit their web page on the
20 internet at http://www.fsf.org/licenses/lgpl.html.
22 Alternatively, the contents of this file may be used under the terms of the
23 Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public
24 License, as published by the Free Software Foundation, either version 2
25 of the License or (at your option) any later version.
26 */
27 #include "inc/Main.h"
28 #include "inc/debug.h"
29 #include "inc/Endian.h"
30 #include "inc/Pass.h"
31 #include <cstring>
32 #include <cstdlib>
33 #include <cassert>
34 #include "inc/Segment.h"
35 #include "inc/Code.h"
36 #include "inc/Rule.h"
37 #include "inc/Error.h"
39 using namespace graphite2;
40 using vm::Machine;
41 typedef Machine::Code Code;
44 Pass::Pass()
45 : m_silf(0),
46 m_cols(0),
47 m_rules(0),
48 m_ruleMap(0),
49 m_startStates(0),
50 m_transitions(0),
51 m_states(0),
52 m_flags(0),
53 m_iMaxLoop(0),
54 m_numGlyphs(0),
55 m_numRules(0),
56 m_numStates(0),
57 m_numTransition(0),
58 m_numSuccess(0),
59 m_numColumns(0),
60 m_minPreCtxt(0),
61 m_maxPreCtxt(0)
62 {
63 }
65 Pass::~Pass()
66 {
67 free(m_cols);
68 free(m_startStates);
69 free(m_transitions);
70 free(m_states);
71 free(m_ruleMap);
73 delete [] m_rules;
74 }
76 bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t subtable_base, GR_MAYBE_UNUSED Face & face, Error &e)
77 {
78 const byte * p = pass_start,
79 * const pass_end = p + pass_length;
80 size_t numRanges;
82 if (e.test(pass_length < 40, E_BADPASSLENGTH)) return face.error(e);
83 // Read in basic values
84 m_flags = be::read<byte>(p);
85 m_iMaxLoop = be::read<byte>(p);
86 be::skip<byte>(p,2); // skip maxContext & maxBackup
87 m_numRules = be::read<uint16>(p);
88 be::skip<uint16>(p); // fsmOffset - not sure why we would want this
89 const byte * const pcCode = pass_start + be::read<uint32>(p) - subtable_base,
90 * const rcCode = pass_start + be::read<uint32>(p) - subtable_base,
91 * const aCode = pass_start + be::read<uint32>(p) - subtable_base;
92 be::skip<uint32>(p);
93 m_numStates = be::read<uint16>(p);
94 m_numTransition = be::read<uint16>(p);
95 m_numSuccess = be::read<uint16>(p);
96 m_numColumns = be::read<uint16>(p);
97 numRanges = be::read<uint16>(p);
98 be::skip<uint16>(p, 3); // skip searchRange, entrySelector & rangeShift.
99 assert(p - pass_start == 40);
100 // Perform some sanity checks.
101 if ( e.test(m_numTransition > m_numStates, E_BADNUMTRANS)
102 || e.test(m_numSuccess > m_numStates, E_BADNUMSUCCESS)
103 || e.test(m_numSuccess + m_numTransition < m_numStates, E_BADNUMSTATES)
104 || e.test(numRanges == 0, E_NORANGES))
105 return face.error(e);
107 m_successStart = m_numStates - m_numSuccess;
108 if (e.test(p + numRanges * 6 - 4 > pass_end, E_BADPASSLENGTH)) return face.error(e);
109 m_numGlyphs = be::peek<uint16>(p + numRanges * 6 - 4) + 1;
110 // Calculate the start of various arrays.
111 const byte * const ranges = p;
112 be::skip<uint16>(p, numRanges*3);
113 const byte * const o_rule_map = p;
114 be::skip<uint16>(p, m_numSuccess + 1);
116 // More sanity checks
117 if (e.test(reinterpret_cast<const byte *>(o_rule_map + m_numSuccess*sizeof(uint16)) > pass_end
118 || p > pass_end, E_BADRULEMAPLEN))
119 return face.error(e);
120 const size_t numEntries = be::peek<uint16>(o_rule_map + m_numSuccess*sizeof(uint16));
121 const byte * const rule_map = p;
122 be::skip<uint16>(p, numEntries);
124 if (e.test(p + 2*sizeof(uint8) > pass_end, E_BADPASSLENGTH)) return face.error(e);
125 m_minPreCtxt = be::read<uint8>(p);
126 m_maxPreCtxt = be::read<uint8>(p);
127 if (e.test(m_minPreCtxt > m_maxPreCtxt, E_BADCTXTLENBOUNDS)) return face.error(e);
128 const byte * const start_states = p;
129 be::skip<int16>(p, m_maxPreCtxt - m_minPreCtxt + 1);
130 const uint16 * const sort_keys = reinterpret_cast<const uint16 *>(p);
131 be::skip<uint16>(p, m_numRules);
132 const byte * const precontext = p;
133 be::skip<byte>(p, m_numRules);
134 be::skip<byte>(p); // skip reserved byte
136 if (e.test(p + sizeof(uint16) > pass_end, E_BADCTXTLENS)) return face.error(e);
137 const size_t pass_constraint_len = be::read<uint16>(p);
138 const uint16 * const o_constraint = reinterpret_cast<const uint16 *>(p);
139 be::skip<uint16>(p, m_numRules + 1);
140 const uint16 * const o_actions = reinterpret_cast<const uint16 *>(p);
141 be::skip<uint16>(p, m_numRules + 1);
142 const byte * const states = p;
143 be::skip<int16>(p, m_numTransition*m_numColumns);
144 be::skip<byte>(p); // skip reserved byte
145 if (e.test(p != pcCode, E_BADPASSCCODEPTR) || e.test(p >= pass_end, E_BADPASSLENGTH)) return face.error(e);
146 be::skip<byte>(p, pass_constraint_len);
147 if (e.test(p != rcCode, E_BADRULECCODEPTR) || e.test(p >= pass_end, E_BADPASSLENGTH)
148 || e.test(size_t(rcCode - pcCode) != pass_constraint_len, E_BADCCODELEN)) return face.error(e);
149 be::skip<byte>(p, be::peek<uint16>(o_constraint + m_numRules));
150 if (e.test(p != aCode, E_BADACTIONCODEPTR) || e.test(p >= pass_end, E_BADPASSLENGTH)) return face.error(e);
151 be::skip<byte>(p, be::peek<uint16>(o_actions + m_numRules));
153 // We should be at the end or within the pass
154 if (e.test(p > pass_end, E_BADPASSLENGTH)) return face.error(e);
156 // Load the pass constraint if there is one.
157 if (pass_constraint_len)
158 {
159 face.error_context(face.error_context() + 1);
160 m_cPConstraint = vm::Machine::Code(true, pcCode, pcCode + pass_constraint_len,
161 precontext[0], be::peek<uint16>(sort_keys), *m_silf, face);
162 if (e.test(!m_cPConstraint, E_OUTOFMEM)
163 || e.test(m_cPConstraint.status(), m_cPConstraint.status() + E_CODEFAILURE))
164 return face.error(e);
165 face.error_context(face.error_context() - 1);
166 }
167 if (!readRanges(ranges, numRanges, e)) return face.error(e);
168 if (!readRules(rule_map, numEntries, precontext, sort_keys,
169 o_constraint, rcCode, o_actions, aCode, face, e)) return false;
170 #ifdef GRAPHITE2_TELEMETRY
171 telemetry::category _states_cat(face.tele.states);
172 #endif
173 return readStates(start_states, states, o_rule_map, face, e);
174 }
177 bool Pass::readRules(const byte * rule_map, const size_t num_entries,
178 const byte *precontext, const uint16 * sort_key,
179 const uint16 * o_constraint, const byte *rc_data,
180 const uint16 * o_action, const byte * ac_data,
181 Face & face, Error &e)
182 {
183 const byte * const ac_data_end = ac_data + be::peek<uint16>(o_action + m_numRules);
184 const byte * const rc_data_end = rc_data + be::peek<uint16>(o_constraint + m_numRules);
186 if (e.test(!(m_rules = new Rule [m_numRules]), E_OUTOFMEM)) return face.error(e);
187 precontext += m_numRules;
188 sort_key += m_numRules;
189 o_constraint += m_numRules;
190 o_action += m_numRules;
192 // Load rules.
193 const byte * ac_begin = 0, * rc_begin = 0,
194 * ac_end = ac_data + be::peek<uint16>(o_action),
195 * rc_end = rc_data + be::peek<uint16>(o_constraint);
196 Rule * r = m_rules + m_numRules - 1;
197 for (size_t n = m_numRules; n; --n, --r, ac_end = ac_begin, rc_end = rc_begin)
198 {
199 face.error_context((face.error_context() & 0xFFFF00) + EC_ARULE + ((n - 1) << 24));
200 r->preContext = *--precontext;
201 r->sort = be::peek<uint16>(--sort_key);
202 #ifndef NDEBUG
203 r->rule_idx = n - 1;
204 #endif
205 if (r->sort > 63 || r->preContext >= r->sort || r->preContext > m_maxPreCtxt || r->preContext < m_minPreCtxt)
206 return false;
207 ac_begin = ac_data + be::peek<uint16>(--o_action);
208 --o_constraint;
209 rc_begin = be::peek<uint16>(o_constraint) ? rc_data + be::peek<uint16>(o_constraint) : rc_end;
211 if (ac_begin > ac_end || ac_begin > ac_data_end || ac_end > ac_data_end
212 || rc_begin > rc_end || rc_begin > rc_data_end || rc_end > rc_data_end)
213 return false;
214 r->action = new vm::Machine::Code(false, ac_begin, ac_end, r->preContext, r->sort, *m_silf, face);
215 r->constraint = new vm::Machine::Code(true, rc_begin, rc_end, r->preContext, r->sort, *m_silf, face);
217 if (e.test(!r->action || !r->constraint, E_OUTOFMEM)
218 || e.test(r->action->status() != Code::loaded, r->action->status() + E_CODEFAILURE)
219 || e.test(r->constraint->status() != Code::loaded, r->constraint->status() + E_CODEFAILURE)
220 || e.test(!r->constraint->immutable(), E_MUTABLECCODE))
221 return face.error(e);
222 }
224 // Load the rule entries map
225 face.error_context((face.error_context() & 0xFFFF00) + EC_APASS);
226 RuleEntry * re = m_ruleMap = gralloc<RuleEntry>(num_entries);
227 if (e.test(!re, E_OUTOFMEM)) return face.error(e);
228 for (size_t n = num_entries; n; --n, ++re)
229 {
230 const ptrdiff_t rn = be::read<uint16>(rule_map);
231 if (e.test(rn >= m_numRules, E_BADRULENUM)) return face.error(e);
232 re->rule = m_rules + rn;
233 }
235 return true;
236 }
238 static int cmpRuleEntry(const void *a, const void *b) { return (*(RuleEntry *)a < *(RuleEntry *)b ? -1 :
239 (*(RuleEntry *)b < *(RuleEntry *)a ? 1 : 0)); }
241 bool Pass::readStates(const byte * starts, const byte *states, const byte * o_rule_map, GR_MAYBE_UNUSED Face & face, Error &e)
242 {
243 #ifdef GRAPHITE2_TELEMETRY
244 telemetry::category _states_cat(face.tele.starts);
245 #endif
246 m_startStates = gralloc<uint16>(m_maxPreCtxt - m_minPreCtxt + 1);
247 #ifdef GRAPHITE2_TELEMETRY
248 telemetry::set_category(face.tele.states);
249 #endif
250 m_states = gralloc<State>(m_numStates);
251 #ifdef GRAPHITE2_TELEMETRY
252 telemetry::set_category(face.tele.transitions);
253 #endif
254 m_transitions = gralloc<uint16>(m_numTransition * m_numColumns);
256 if (e.test(!m_startStates || !m_states || !m_transitions, E_OUTOFMEM)) return face.error(e);
257 // load start states
258 for (uint16 * s = m_startStates,
259 * const s_end = s + m_maxPreCtxt - m_minPreCtxt + 1; s != s_end; ++s)
260 {
261 *s = be::read<uint16>(starts);
262 if (e.test(*s >= m_numStates, E_BADSTATE))
263 {
264 face.error_context((face.error_context() & 0xFFFF00) + EC_ASTARTS + ((s - m_startStates) << 24));
265 return face.error(e); // true;
266 }
267 }
269 // load state transition table.
270 for (uint16 * t = m_transitions,
271 * const t_end = t + m_numTransition*m_numColumns; t != t_end; ++t)
272 {
273 *t = be::read<uint16>(states);
274 if (e.test(*t >= m_numStates, E_BADSTATE))
275 {
276 face.error_context((face.error_context() & 0xFFFF00) + EC_ATRANS + (((t - m_transitions) / m_numColumns) << 24));
277 return face.error(e);
278 }
279 }
281 State * s = m_states,
282 * const success_begin = m_states + m_numStates - m_numSuccess;
283 const RuleEntry * rule_map_end = m_ruleMap + be::peek<uint16>(o_rule_map + m_numSuccess*sizeof(uint16));
284 for (size_t n = m_numStates; n; --n, ++s)
285 {
286 RuleEntry * const begin = s < success_begin ? 0 : m_ruleMap + be::read<uint16>(o_rule_map),
287 * const end = s < success_begin ? 0 : m_ruleMap + be::peek<uint16>(o_rule_map);
289 if (e.test(begin >= rule_map_end || end > rule_map_end || begin > end, E_BADRULEMAPPING))
290 {
291 face.error_context((face.error_context() & 0xFFFF00) + EC_ARULEMAP + (n << 24));
292 return face.error(e);
293 }
294 s->rules = begin;
295 s->rules_end = (end - begin <= FiniteStateMachine::MAX_RULES)? end :
296 begin + FiniteStateMachine::MAX_RULES;
297 qsort(begin, end - begin, sizeof(RuleEntry), &cmpRuleEntry);
298 }
300 return true;
301 }
303 bool Pass::readRanges(const byte * ranges, size_t num_ranges, Error &e)
304 {
305 m_cols = gralloc<uint16>(m_numGlyphs);
306 if (e.test(!m_cols, E_OUTOFMEM)) return false;
307 memset(m_cols, 0xFF, m_numGlyphs * sizeof(uint16));
308 for (size_t n = num_ranges; n; --n)
309 {
310 uint16 * ci = m_cols + be::read<uint16>(ranges),
311 * ci_end = m_cols + be::read<uint16>(ranges) + 1,
312 col = be::read<uint16>(ranges);
314 if (e.test(ci >= ci_end || ci_end > m_cols+m_numGlyphs || col >= m_numColumns, E_BADRANGE))
315 return false;
317 // A glyph must only belong to one column at a time
318 while (ci != ci_end && *ci == 0xffff)
319 *ci++ = col;
321 if (e.test(ci != ci_end, E_BADRANGE))
322 return false;
323 }
324 return true;
325 }
328 void Pass::runGraphite(Machine & m, FiniteStateMachine & fsm) const
329 {
330 Slot *s = m.slotMap().segment.first();
331 if (!s || !testPassConstraint(m)) return;
332 Slot *currHigh = s->next();
334 #if !defined GRAPHITE2_NTRACING
335 if (fsm.dbgout) *fsm.dbgout << "rules" << json::array;
336 json::closer rules_array_closer(fsm.dbgout);
337 #endif
339 m.slotMap().highwater(currHigh);
340 int lc = m_iMaxLoop;
341 do
342 {
343 findNDoRule(s, m, fsm);
344 if (s && (m.slotMap().highpassed() || s == m.slotMap().highwater() || --lc == 0)) {
345 if (!lc)
346 {
347 // if (dbgout) *dbgout << json::item << json::flat << rule_event(-1, s, 1);
348 s = m.slotMap().highwater();
349 }
350 lc = m_iMaxLoop;
351 if (s)
352 m.slotMap().highwater(s->next());
353 }
354 } while (s);
355 }
357 bool Pass::runFSM(FiniteStateMachine& fsm, Slot * slot) const
358 {
359 fsm.reset(slot, m_maxPreCtxt);
360 if (fsm.slots.context() < m_minPreCtxt)
361 return false;
363 uint16 state = m_startStates[m_maxPreCtxt - fsm.slots.context()];
364 uint8 free_slots = SlotMap::MAX_SLOTS;
365 do
366 {
367 fsm.slots.pushSlot(slot);
368 if (--free_slots == 0
369 || slot->gid() >= m_numGlyphs
370 || m_cols[slot->gid()] == 0xffffU
371 || state >= m_numTransition)
372 return free_slots != 0;
374 const uint16 * transitions = m_transitions + state*m_numColumns;
375 state = transitions[m_cols[slot->gid()]];
376 if (state >= m_successStart)
377 fsm.rules.accumulate_rules(m_states[state]);
379 slot = slot->next();
380 } while (state != 0 && slot);
382 fsm.slots.pushSlot(slot);
383 return true;
384 }
386 #if !defined GRAPHITE2_NTRACING
388 inline
389 Slot * input_slot(const SlotMap & slots, const int n)
390 {
391 Slot * s = slots[slots.context() + n];
392 if (!s->isCopied()) return s;
394 return s->prev() ? s->prev()->next() : (s->next() ? s->next()->prev() : slots.segment.last());
395 }
397 inline
398 Slot * output_slot(const SlotMap & slots, const int n)
399 {
400 Slot * s = slots[slots.context() + n - 1];
401 return s ? s->next() : slots.segment.first();
402 }
404 #endif //!defined GRAPHITE2_NTRACING
406 void Pass::findNDoRule(Slot * & slot, Machine &m, FiniteStateMachine & fsm) const
407 {
408 assert(slot);
410 if (runFSM(fsm, slot))
411 {
412 // Search for the first rule which passes the constraint
413 const RuleEntry * r = fsm.rules.begin(),
414 * const re = fsm.rules.end();
415 while (r != re && !testConstraint(*r->rule, m)) ++r;
417 #if !defined GRAPHITE2_NTRACING
418 if (fsm.dbgout)
419 {
420 if (fsm.rules.size() != 0)
421 {
422 *fsm.dbgout << json::item << json::object;
423 dumpRuleEventConsidered(fsm, *r);
424 if (r != re)
425 {
426 const int adv = doAction(r->rule->action, slot, m);
427 dumpRuleEventOutput(fsm, *r->rule, slot);
428 if (r->rule->action->deletes()) fsm.slots.collectGarbage();
429 adjustSlot(adv, slot, fsm.slots);
430 *fsm.dbgout << "cursor" << objectid(dslot(&fsm.slots.segment, slot))
431 << json::close; // Close RuelEvent object
433 return;
434 }
435 else
436 {
437 *fsm.dbgout << json::close // close "considered" array
438 << "output" << json::null
439 << "cursor" << objectid(dslot(&fsm.slots.segment, slot->next()))
440 << json::close;
441 }
442 }
443 }
444 else
445 #endif
446 {
447 if (r != re)
448 {
449 const int adv = doAction(r->rule->action, slot, m);
450 if (r->rule->action->deletes()) fsm.slots.collectGarbage();
451 adjustSlot(adv, slot, fsm.slots);
452 return;
453 }
454 }
455 }
457 slot = slot->next();
458 }
460 #if !defined GRAPHITE2_NTRACING
462 void Pass::dumpRuleEventConsidered(const FiniteStateMachine & fsm, const RuleEntry & re) const
463 {
464 *fsm.dbgout << "considered" << json::array;
465 for (const RuleEntry *r = fsm.rules.begin(); r != &re; ++r)
466 {
467 if (r->rule->preContext > fsm.slots.context()) continue;
468 *fsm.dbgout << json::flat << json::object
469 << "id" << r->rule - m_rules
470 << "failed" << true
471 << "input" << json::flat << json::object
472 << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, -r->rule->preContext)))
473 << "length" << r->rule->sort
474 << json::close // close "input"
475 << json::close; // close Rule object
476 }
477 }
480 void Pass::dumpRuleEventOutput(const FiniteStateMachine & fsm, const Rule & r, Slot * const last_slot) const
481 {
482 *fsm.dbgout << json::item << json::flat << json::object
483 << "id" << &r - m_rules
484 << "failed" << false
485 << "input" << json::flat << json::object
486 << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, 0)))
487 << "length" << r.sort - r.preContext
488 << json::close // close "input"
489 << json::close // close Rule object
490 << json::close // close considered array
491 << "output" << json::object
492 << "range" << json::flat << json::object
493 << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, 0)))
494 << "end" << objectid(dslot(&fsm.slots.segment, last_slot))
495 << json::close // close "input"
496 << "slots" << json::array;
497 const Position rsb_prepos = last_slot ? last_slot->origin() : fsm.slots.segment.advance();
498 fsm.slots.segment.positionSlots(0);
500 for(Slot * slot = output_slot(fsm.slots, 0); slot != last_slot; slot = slot->next())
501 *fsm.dbgout << dslot(&fsm.slots.segment, slot);
502 *fsm.dbgout << json::close // close "slots"
503 << "postshift" << (last_slot ? last_slot->origin() : fsm.slots.segment.advance()) - rsb_prepos
504 << json::close; // close "output" object
506 }
508 #endif
511 inline
512 bool Pass::testPassConstraint(Machine & m) const
513 {
514 if (!m_cPConstraint) return true;
516 assert(m_cPConstraint.constraint());
518 m.slotMap().reset(*m.slotMap().segment.first(), 0);
519 m.slotMap().pushSlot(m.slotMap().segment.first());
520 vm::slotref * map = m.slotMap().begin();
521 const uint32 ret = m_cPConstraint.run(m, map);
523 #if !defined GRAPHITE2_NTRACING
524 json * const dbgout = m.slotMap().segment.getFace()->logger();
525 if (dbgout)
526 *dbgout << "constraint" << (ret && m.status() == Machine::finished);
527 #endif
529 return ret && m.status() == Machine::finished;
530 }
533 bool Pass::testConstraint(const Rule & r, Machine & m) const
534 {
535 const uint16 curr_context = m.slotMap().context();
536 if (unsigned(r.sort - r.preContext) > m.slotMap().size() - curr_context
537 || curr_context - r.preContext < 0) return false;
538 if (!*r.constraint) return true;
539 assert(r.constraint->constraint());
541 vm::slotref * map = m.slotMap().begin() + curr_context - r.preContext;
542 for (int n = r.sort; n && map; --n, ++map)
543 {
544 if (!*map) continue;
545 const int32 ret = r.constraint->run(m, map);
546 if (!ret || m.status() != Machine::finished)
547 return false;
548 }
550 return true;
551 }
554 void SlotMap::collectGarbage()
555 {
556 for(Slot **s = begin(), *const *const se = end() - 1; s != se; ++s) {
557 Slot *& slot = *s;
558 if(slot->isDeleted() || slot->isCopied())
559 segment.freeSlot(slot);
560 }
561 }
565 int Pass::doAction(const Code *codeptr, Slot * & slot_out, vm::Machine & m) const
566 {
567 assert(codeptr);
568 if (!*codeptr) return 0;
569 SlotMap & smap = m.slotMap();
570 vm::slotref * map = &smap[smap.context()];
571 smap.highpassed(false);
573 int32 ret = codeptr->run(m, map);
575 if (m.status() != Machine::finished)
576 {
577 slot_out = NULL;
578 smap.highwater(0);
579 return 0;
580 }
582 slot_out = *map;
583 return ret;
584 }
587 void Pass::adjustSlot(int delta, Slot * & slot_out, SlotMap & smap) const
588 {
589 if (delta < 0)
590 {
591 if (!slot_out)
592 {
593 slot_out = smap.segment.last();
594 ++delta;
595 if (smap.highpassed() && !smap.highwater())
596 smap.highpassed(false);
597 }
598 while (++delta <= 0 && slot_out)
599 {
600 if (smap.highpassed() && smap.highwater() == slot_out)
601 smap.highpassed(false);
602 slot_out = slot_out->prev();
603 }
604 }
605 else if (delta > 0)
606 {
607 if (!slot_out)
608 {
609 slot_out = smap.segment.first();
610 --delta;
611 }
612 while (--delta >= 0 && slot_out)
613 {
614 slot_out = slot_out->next();
615 if (slot_out == smap.highwater() && slot_out)
616 smap.highpassed(true);
617 }
618 }
619 }