fdp
Version:
Finite Domain Problem reduction system
1,400 lines (1,217 loc) • 105 kB
JavaScript
// problem optimizer
// take an input problem and determine whether constraints can be pruned
// or domains cut before actually generating their propagators
import {
$CHANGED,
$REJECTED,
$SOLVED,
$STABLE,
ASSERT,
ASSERT_NORDOM,
getTerm,
TRACE,
TRACE_MORPH,
THROW,
} from '../../fdlib/src/helpers';
import {
domain__debug,
domain_createEmpty,
domain_createBoolyPair,
domain_createValue,
domain_divby,
domain_getValue,
domain_hasNoZero,
domain_hasZero,
domain_intersection,
domain_intersectionValue,
domain_invMul,
domain_isBool,
domain_isBooly,
domain_isBoolyPair,
domain_isSolved,
domain_isZero,
domain_max,
domain_min,
domain_minus,
domain_mul,
domain_plus,
domain_removeGte,
domain_removeGtUnsafe,
domain_removeLte,
domain_removeLtUnsafe,
domain_removeValue,
domain_sharesNoElements,
domain_size,
} from '../../fdlib/src/domain';
import {
ML_ALL,
ML_NOLEAF,
ML_NOBOOL,
ML_DIFF,
ML_DIV,
ML_IMP,
ML_ISALL,
ML_ISDIFF,
ML_ISLT,
ML_ISLTE,
ML_ISNALL,
ML_ISNONE,
ML_ISSAME,
ML_ISSOME,
ML_JMP,
ML_JMP32,
ML_LT,
ML_LTE,
ML_MINUS,
ML_NALL,
ML_NIMP,
ML_NONE,
ML_NOOP,
ML_NOOP2,
ML_NOOP3,
ML_NOOP4,
ML_PRODUCT,
ML_SAME,
ML_SOME,
ML_START,
ML_STOP,
ML_SUM,
ML_XNOR,
ML_XOR,
OFFSET_C_A,
OFFSET_C_B,
//OFFSET_C_C,
OFFSET_C_R,
SIZEOF_V,
SIZEOF_W,
SIZEOF_VVV,
SIZEOF_C,
SIZEOF_C_2,
SIZEOF_CR_2,
ml__debug,
ml__opName,
ml_c2c2,
ml_cr2c2,
ml_cr2cr2,
ml_dec8,
ml_dec16,
ml_dec32,
ml_enc8,
ml_enc16,
ml_eliminate,
ml_compileJumpAndConsolidate,
ml_compileJumpSafe,
ml_heapSort16bitInline,
ml_validateSkeleton,
ml_vvv2c2,
} from './ml';
import {
m2d__debug,
} from './ml2dsl';
// BODY_START
function min_run(ml, problem, domains, names, firstRun, once) {
TRACE('min_run, loop:', firstRun, ', byte code:', ml.length < 50 ? ml.join(' ') : '<big>');
TRACE(ml__debug(ml, 0, 20, problem));
// now we can access the ml in terms of bytes, jeuj
let state = min_optimizeConstraints(ml, problem, domains, names, firstRun, once);
if (state === $SOLVED) {
TRACE('minimizing solved it!', state); // all constraints have been eliminated
} else if (state === $REJECTED) {
TRACE('minimizing rejected it!', state); // an empty domain was found or a literal failed a test
} else {
TRACE('pre-optimization finished, not yet solved');
}
return state;
}
function min_optimizeConstraints(ml, problem, domains, names, firstRun, once) {
TRACE('min_optimizeConstraints', ml.length < 50 ? ml.join(' ') : '');
TRACE(problem.domains.length > 100 ? '' : ' <' + problem.domains.map((d, i) => i + ' : ' + problem.varNames[i] + ' : ' + domain__debug(problem.getDomain(i))).join('>, <') + '>');
TRACE('minimize sweep, ml len=', ml.length, ', firstRun=', firstRun, 'once=', once);
let varChanged = true;
let onlyJumps = false;
let emptyDomain = false;
let lastPcOffset = 0;
let lastOp = 0;
let pc = 0;
let loops = 0;
let constraints = 0; // count a constraint going forward, ignore jumps, reduce when restarting from same pc
let restartedRelevantOp = false; // annoying, but restartedRelevantOp could mean more scrubbing is required. but it may not...
let term = getTerm();
let {
addVar,
addAlias,
getAlias,
getDomain,
setDomain,
solveStack,
} = problem;
//function addPseudoAlias(indexA, indexB, A, B) {
// ASSERT(domain_isBoolyPair(A) && domain_isBoolyPair(B), 'assuming A and B are booly pairs');
// ASSERT(A !== B, 'booly pairs that are equal are regular aliases so dont call this function on them');
//
// addAlias(indexA, indexB); // A is replaced by B
//
// // consider them aliases but add a special solve stack
// // entry to restore the max to A if B turns out nonzero
// solveStack.push((_, force, getDomain, setDomain) => {
// TRACE(' - deduper psuedo alias; was:', domain__debug(A), '!^', domain__debug(B), ', now:', domain__debug(getDomain(indexA)), '!^', domain__debug(getDomain(indexB)));
// let vB = force(indexB);
// TRACE(' - B forced to', vB);
// if (vB > 0) {
// setDomain(indexA, domain_removeValue(A, 0), true, true);
// }
//
// ASSERT(getDomain(indexA));
// ASSERT(getDomain(indexB));
// ASSERT(domain_isSolved(getDomain(indexA)));
// ASSERT(domain_isSolved(getDomain(indexB)));
// ASSERT((domain_getValue(getDomain(indexA)) === 0) === (domain_getValue(getDomain(indexB)) === 0));
// });
//}
while (!onlyJumps && (varChanged || restartedRelevantOp)) {
++loops;
//term.log('- looping', loops);
term.time('-> min_loop ' + loops);
TRACE('min outer loop');
varChanged = false;
onlyJumps = true; // until proven otherwise
restartedRelevantOp = false;
pc = 0;
constraints = 0;
let ops = min_innerLoop();
TRACE('changed?', varChanged, 'only jumps?', onlyJumps, 'empty domain?', emptyDomain, 'restartedRelevantOp?', restartedRelevantOp);
if (emptyDomain) {
term.log('Empty domain at', lastPcOffset, 'for opcode', lastOp, [ml__debug(ml, lastPcOffset, 1, problem)], ml.slice(lastPcOffset, lastPcOffset + 10));
term.error('Empty domain, problem rejected');
}
term.timeEnd('-> min_loop ' + loops);
term.log(' - ops this loop:', ops, 'constraints:', constraints);
if (emptyDomain) return $REJECTED;
if (onlyJumps) return $SOLVED;
TRACE('\n## Intermediate state: ##');
TRACE(ml__debug(ml, 0, 20, problem));
TRACE(m2d__debug(problem));
if (once) break;
firstRun = false;
}
if (loops === 1) return $STABLE;
return $CHANGED;
// ####################################################################################
function readIndex(ml, offset) {
// get an index from ml. check for alias, and if so, immediately compile back the alias to improve future fetches.
let index = ml_dec16(ml, offset);
let alias = getAlias(index);
if (alias !== index) ml_enc16(ml, offset, alias);
return alias;
}
function getDomainFast(index) {
ASSERT(index >= 0 && index <= 0xffff, 'expecting valid index', index);
ASSERT(getAlias(index) === index, 'index should be unaliased', index, getAlias(index));
let domain = getDomain(index, true);
ASSERT(domain, 'domain cant be falsy', domain);
ASSERT_NORDOM(domain);
if (!domain) setEmpty(index, 'bad state (empty domain should have been detected sooner)');
return domain;
}
function updateDomain(index, domain, desc) {
TRACE(' - updateDomain; {', index, '} updated from', domain__debug(getDomain(index)), 'to', domain__debug(domain));
ASSERT(!domain || domain_intersection(getDomain(index), domain), 'should never add new values to a domain, only remove them', 'index=', index, 'old=', domain__debug(getDomain(index)), 'new=', domain__debug(domain), 'desc=', desc);
setDomain(index, domain, false, true);
if (domain) {
varChanged = true;
} else {
TRACE(' - (updateDomain: EMPTY DOMAIN)');
emptyDomain = true;
}
return emptyDomain;
}
function setEmpty(index, desc) {
TRACE(' - :\'( setEmpty({', index, '})', desc);
emptyDomain = true;
if (index >= 0) updateDomain(index, domain_createEmpty(), 'explicitly empty' + (desc ? '; ' + desc : ''));
}
function min_innerLoop() {
let ops = 0;
onlyJumps = true;
let wasMetaOp = false; // jumps, start, stop, etc. not important if they "change"
while (pc < ml.length && !emptyDomain) {
++ops;
++constraints;
wasMetaOp = false;
let pcStart = pc;
lastPcOffset = pc;
let op = ml[pc];
lastOp = op;
//ASSERT(ml_validateSkeleton(ml, 'min_innerLoop'));
TRACE(' # CU pc=' + pcStart, ', op=', op, ml__opName(op));
TRACE(' -> op: ' + ml__debug(ml, pc, 1, problem, true));
switch (op) {
case ML_START:
if (pc !== 0) {
TRACE('reading a op=zero at position ' + pc + ' which should not happen', ml.slice(Math.max(pc - 100, 0), pc), '<here>', ml.slice(pc, pc + 100));
return THROW(' ! optimizer problem @', pc);
}
wasMetaOp = true;
++pc;
--constraints; // not a constraint
break;
case ML_STOP:
TRACE(' ! good end @', pcStart);
wasMetaOp = true;
--constraints; // not a constraint
return ops;
case ML_LT:
TRACE('- lt vv @', pcStart);
min_lt(ml, pc);
break;
case ML_LTE:
TRACE('- lte vv @', pcStart);
min_lte(ml, pc);
break;
case ML_NONE:
TRACE('- none @', pcStart);
min_none(ml, pc);
break;
case ML_XOR:
TRACE('- xor @', pcStart);
min_xor(ml, pc);
break;
case ML_XNOR:
TRACE('- xnor @', pcStart);
min_xnor(ml, pc);
break;
case ML_IMP:
TRACE('- imp @', pcStart);
min_imp(ml, pc);
break;
case ML_NIMP:
TRACE('- nimp @', pcStart);
min_nimp(ml, pc);
break;
case ML_DIFF:
TRACE('- diff @', pcStart);
min_diff(ml, pc);
break;
case ML_ALL:
TRACE('- all() @', pcStart);
min_all(ml, pc);
break;
case ML_ISDIFF:
TRACE('- isdiff @', pcStart);
min_isDiff(ml, pc);
break;
case ML_NALL:
TRACE('- nall @', pcStart);
min_nall(ml, pc);
break;
case ML_SAME:
TRACE('- same @', pcStart);
min_same(ml, pc);
break;
case ML_SOME:
TRACE('- some @', pcStart);
min_some(ml, pc);
break;
case ML_ISLT:
TRACE('- islt vvv @', pcStart);
min_isLt(ml, pc);
break;
case ML_ISLTE:
TRACE('- islte vvv @', pcStart);
min_isLte(ml, pc);
break;
case ML_ISALL:
TRACE('- isall @', pcStart);
min_isAll(ml, pc);
break;
case ML_ISNALL:
TRACE('- isnall @', pcStart);
min_isNall(ml, pc);
break;
case ML_ISSAME:
TRACE('- issame @', pcStart);
min_isSame(ml, pc);
break;
case ML_ISSOME:
TRACE('- issome @', pcStart);
min_isSome(ml, pc);
break;
case ML_ISNONE:
TRACE('- isnone @', pcStart);
min_isNone(ml, pc);
break;
case ML_MINUS:
TRACE('- minus @', pcStart);
min_minus(ml, pc);
break;
case ML_DIV:
TRACE('- div @', pcStart);
min_div(ml, pc);
break;
case ML_SUM:
TRACE('- sum @', pcStart);
min_sum(ml, pc);
break;
case ML_PRODUCT:
TRACE('- product @', pcStart);
min_product(ml, pc);
break;
case ML_NOBOOL:
TRACE('- nobool @', pc);
pc += SIZEOF_V;
wasMetaOp = true;
break;
case ML_NOLEAF:
TRACE('- noleaf @', pc);
pc += SIZEOF_V;
wasMetaOp = true;
break;
case ML_NOOP:
TRACE('- noop @', pc);
min_moveTo(ml, pc, 1);
--constraints; // not a constraint
wasMetaOp = true;
break;
case ML_NOOP2:
TRACE('- noop2 @', pc);
min_moveTo(ml, pc, 2);
--constraints; // not a constraint
wasMetaOp = true;
break;
case ML_NOOP3:
TRACE('- noop3 @', pc);
min_moveTo(ml, pc, 3);
--constraints; // not a constraint
wasMetaOp = true;
break;
case ML_NOOP4:
TRACE('- noop4 @', pc);
min_moveTo(ml, pc, 4);
--constraints; // not a constraint
wasMetaOp = true;
break;
case ML_JMP:
TRACE('- jmp @', pc);
min_moveTo(ml, pc, SIZEOF_V + ml_dec16(ml, pc + 1));
--constraints; // not a constraint
wasMetaOp = true;
break;
case ML_JMP32:
TRACE('- jmp32 @', pc);
min_moveTo(ml, pc, SIZEOF_W + ml_dec32(ml, pc + 1));
--constraints; // not a constraint
wasMetaOp = true;
break;
default:
THROW('(mn) unknown op: 0x' + op.toString(16), ' at', pc);
}
if (pc === pcStart && !emptyDomain) {
TRACE(' - restarting op from same pc...');
if (!wasMetaOp) restartedRelevantOp = true; // TODO: undo this particular step if the restart results in the offset becoming a jmp?
--constraints; // constraint may have been eliminated
}
}
if (emptyDomain) {
return ops;
}
return THROW('Derailed; expected to find STOP before EOF');
}
function min_moveTo(ml, offset, len) {
TRACE(' - trying to move from', offset, 'to', offset + len, 'delta = ', len);
switch (ml_dec8(ml, offset + len)) {
case ML_NOOP:
case ML_NOOP2:
case ML_NOOP3:
case ML_NOOP4:
case ML_JMP:
case ML_JMP32:
TRACE('- moving to another jump so merging them now');
ml_compileJumpAndConsolidate(ml, offset, len);
pc = offset; // restart, make sure the merge worked
break;
default:
pc = offset + len;
break;
}
}
function min_same(ml, offset) {
// loop through the args and alias each one to the previous. then eliminate the constraint. it is an artifact.
let argCount = ml_dec16(ml, offset + 1);
TRACE(' = min_same', argCount, 'x');
if (argCount === 2) {
if (readIndex(ml, offset + OFFSET_C_A) === readIndex(ml, offset + OFFSET_C_B)) {
TRACE(' - argcount=2 and A==B so eliminating constraint');
ml_eliminate(ml, offset, SIZEOF_C_2);
return;
}
}
if (argCount > 1) {
TRACE(' - aliasing all args to the first arg, intersecting all domains, and eliminating constraint');
let firstIndex = readIndex(ml, offset + SIZEOF_C);
let F = getDomain(firstIndex, true);
TRACE(' - indexF=', firstIndex, ', F=', domain__debug(F));
for (let i = 1; i < argCount; ++i) {
let indexD = readIndex(ml, offset + SIZEOF_C + i * 2);
if (indexD !== firstIndex) {
let D = getDomain(indexD, true);
TRACE(' - pos:', i, ', aliasing index', indexD, 'to F, intersecting', domain__debug(D), 'with', domain__debug(F), 'to', domain__debug(domain_intersection(F, D)));
F = intersectAndAlias(indexD, firstIndex, D, F);
if (!F) {
TRACE(' !! Caused an empty domain. Failing.');
break;
}
}
}
}
TRACE(' - eliminating same() constraint');
ml_eliminate(ml, offset, SIZEOF_C + argCount * 2);
}
function min_diff_2(ml, offset) {
TRACE(' - min_diff_2');
ASSERT(ml_dec16(ml, offset + 1) === 2, 'should be arg count = 2');
let offsetA = offset + OFFSET_C_A;
let offsetB = offset + OFFSET_C_B;
let indexA = readIndex(ml, offsetA);
let indexB = readIndex(ml, offsetB);
let A = getDomainFast(indexA);
let B = getDomainFast(indexB);
TRACE(' -', indexA, '!=', indexB, ' -> ', domain__debug(A), '!=', domain__debug(B));
if (!A || !B) return true;
if (indexA === indexB) {
TRACE(' - A != A, falsum, artifact case');
setEmpty(indexA, 'X!=X falsum');
return true;
}
let solved = false;
// if either is solved then the other domain should
// become the result of unsolved_set "minus" solved_set
let vA = domain_getValue(A);
if (vA >= 0) {
let oB = B;
B = domain_removeValue(B, vA);
if (oB !== B && updateDomain(indexB, B, 'A != B with A solved')) return true;
solved = true;
} else {
let vB = domain_getValue(B);
if (domain_getValue(B) >= 0) {
let oA = A;
A = domain_removeValue(A, vB);
if (A !== oA && updateDomain(indexA, A, 'A != B with B solved')) return true;
solved = true;
}
}
// if the two domains share no elements the constraint is already satisfied
if (!solved && !domain_intersection(A, B)) solved = true;
TRACE(' ->', domain__debug(A), '!=', domain__debug(B), ', solved?', solved);
// solved if the two domains (now) intersect to an empty domain
if (solved) {
TRACE(' - No element overlapping between', indexA, 'and', indexB, 'left so we can eliminate this diff');
ASSERT(domain_sharesNoElements(A, B), 'if A or B solves, the code should have solved the diff');
ml_eliminate(ml, offset, SIZEOF_C_2);
return true;
}
TRACE(' - min_diff_2 changed nothing');
return false;
}
function min_lt(ml, offset) {
let offsetA = offset + OFFSET_C_A;
let offsetB = offset + OFFSET_C_B;
let indexA = readIndex(ml, offsetA);
let indexB = readIndex(ml, offsetB);
let A = getDomainFast(indexA);
let B = getDomainFast(indexB);
TRACE(' = min_lt', indexA, '<', indexB, ' -> ', domain__debug(A), '<', domain__debug(B));
if (indexA === indexB) return setEmpty(indexA, 'X<X falsum'); // (relevant) artifact case
if (!A || !B) return;
// relative comparison is easy; cut away any non-intersecting
// values that violate the desired outcome. only when a A and
// B have multiple intersecting values we have to keep this
// constraint
let oA = A;
A = domain_removeGte(A, domain_max(B));
if (A !== oA) {
TRACE(' - updating A to', domain__debug(A));
if (updateDomain(indexA, A, 'A lt B')) return;
}
let oB = B;
B = domain_removeLte(B, domain_min(A));
if (B !== oB) {
TRACE(' - updating B to', domain__debug(B));
if (updateDomain(indexB, B, 'A lt B')) return;
}
// any value in A must be < any value in B
if (domain_max(A) < domain_min(B)) {
TRACE(' - Eliminating lt because max(A)<min(B)');
ml_eliminate(ml, offset, SIZEOF_C_2);
} else {
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + SIZEOF_C_2;
}
}
function min_lte(ml, offset) {
let offsetA = offset + OFFSET_C_A;
let offsetB = offset + OFFSET_C_B;
let indexA = readIndex(ml, offsetA);
let indexB = readIndex(ml, offsetB);
let A = getDomainFast(indexA);
let B = getDomainFast(indexB);
TRACE(' = min_lte', indexA, '<=', indexB, ' -> ', domain__debug(A), '<=', domain__debug(B));
if (!A || !B) return;
if (indexA === indexB) {
TRACE(' - Eliminating lte because max(A)<=min(A) is a tautology (once solved)');
ml_eliminate(ml, offset, SIZEOF_C_2);
return;
}
// relative comparison is easy; cut away any non-intersecting
// values that violate the desired outcome. only when a A and
// B have multiple intersecting values we have to keep this
// constraint
TRACE(' - index A!=B so remove all >max(B) from A', domain__debug(A), domain_max(B), '->', domain__debug(domain_removeGtUnsafe(A, domain_max(B))));
let oA = A;
A = domain_removeGtUnsafe(A, domain_max(B));
if (A !== oA) {
TRACE(' - Updating A to', domain__debug(A));
if (updateDomain(indexA, A, 'A lte B')) return;
}
// A is (now) empty so just remove it
let oB = B;
B = domain_removeLtUnsafe(B, domain_min(A));
if (B !== oB) {
TRACE(' - Updating B to', domain__debug(B));
if (updateDomain(indexB, B, 'A lte B')) return;
}
TRACE(' ->', domain__debug(A), '<=', domain__debug(B), ', bp?', domain_isBoolyPair(A), '<=', domain_isBoolyPair(B), ', max:', domain_max(A), '<=', domain_max(B));
// any value in A must be < any value in B
if (domain_max(A) <= domain_min(B)) {
TRACE(' - Eliminating lte because max(A)<=min(B)');
ml_eliminate(ml, offset, SIZEOF_C_2);
} else if (domain_isBoolyPair(A) && domain_isBoolyPair(B) && domain_max(A) <= domain_max(B)) {
TRACE(' - A and B boolypair with max(A)<=max(B) so this is implication');
TRACE_MORPH('A <= B', 'B -> A');
ml_c2c2(ml, offset, 2, ML_IMP, indexA, indexB);
// have to add a solvestack entry to prevent a solution [01]->1 which would satisfy IMP but not LTE
solveStack.push((_, force, getDomain, setDomain) => {
TRACE(' - min_lte; enforcing LTE', indexA, '<=', indexB, ' => ', domain__debug(getDomain(indexA)), '<=', domain__debug(getDomain(indexB)));
let A = getDomain(indexA);
let B = getDomain(indexB);
if (domain_hasNoZero(A)) {
B = domain_removeValue(B, 0);
setDomain(indexB, B);
} else if (domain_isZero(B) || domain_isBooly(A)) {
A = domain_removeGtUnsafe(A, 0);
setDomain(indexA, A);
}
ASSERT(getDomain(indexA));
ASSERT(getDomain(indexB));
ASSERT(domain_max(getDomain(indexA)) <= domain_min(getDomain(indexB)), 'must hold lte', domain__debug(A), '<=', domain__debug(B));
});
} else {
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + SIZEOF_C_2;
}
}
function min_nall(ml, offset) {
let offsetCount = offset + 1;
let argCount = ml_dec16(ml, offsetCount);
let offsetArgs = offset + SIZEOF_C;
let opSize = SIZEOF_C + argCount * 2;
TRACE(' = min_nall', argCount, 'x');
TRACE(' - ml for this nall:', ml.slice(offset, offset + opSize).join(' '));
TRACE(' -', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)));
TRACE(' -', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))));
if (!argCount) return setEmpty(-1, 'nall without args is probably a bug');
if (argCount === 2) {
if (min_nall_2(ml, offset)) return;
}
let countStart = argCount;
let lastIndex = -1;
let lastDomain;
// a nall only ensures at least one of its arg solves to zero
for (let i = argCount - 1; i >= 0; --i) { // backwards because we're pruning dud indexes
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - loop i=', i, 'index=', index, 'domain=', domain__debug(domain));
if (!domain) return;
if (domain_min(domain) > 0 || lastIndex === index) {
// remove var from list
TRACE(lastIndex === index ? ' - removing redundant dupe var from nall' : ' - domain contains no zero so remove var from this constraint');
// now
// - move all indexes bigger than the current back one position
// - compile the new count back in
// - compile a NOOP in the place of the last element
TRACE(' - moving further domains one space forward (from ', i + 1, ' / ', argCount, ')');
min_spliceArgSlow(ml, offsetArgs, argCount, i, false);
--argCount;
} else if (domain_isZero(domain)) {
// remove constraint
TRACE(' - domain solved to zero so constraint is satisfied');
ml_eliminate(ml, offset, SIZEOF_C + 2 * countStart);
return;
} else {
// arg contains a 0 and is unsolved
TRACE(' - domain contains zero and is not solved so leave it alone');
lastIndex = index;
lastDomain = domain;
}
}
if (argCount === 0) {
TRACE(' - Nall has no var left to be zero; rejecting problem');
// this is a bad state: all vars were removed from this constraint which
// means none of the args were zero and the constraint doesnt hold
return setEmpty(lastIndex, 'nall; none of the args were zero');
} else if (argCount === 1) {
TRACE(' - Nall has one var left; forcing it to zero');
// force set last index to zero and remove constraint. this should not
// reject (because then the var would have been removed in loop above)
// but do it "safe" anyways, just in case.
let domain = domain_removeGtUnsafe(lastDomain, 0);
if (lastDomain !== domain && updateDomain(lastIndex, domain)) return;
ml_eliminate(ml, offset, SIZEOF_C + 2 * countStart);
} else if (countStart !== argCount) {
TRACE(' - recording new argcount and freeing up space');
ml_enc16(ml, offsetCount, argCount); // write new count
let free = (countStart - argCount) * 2;
ml_compileJumpSafe(ml, offset + opSize - free, free);
// note: still have to restart op because ml_jump may have clobbered the old end of the op with a new jump
} else {
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + opSize;
}
}
function min_nall_2(ml, offset) {
let offsetA = offset + OFFSET_C_A;
let offsetB = offset + OFFSET_C_B;
let indexA = readIndex(ml, offsetA);
let indexB = readIndex(ml, offsetB);
let A = getDomainFast(indexA);
let B = getDomainFast(indexB);
TRACE(' = min_nall_2', indexA, '!&', indexB, ' -> ', domain__debug(A), '!&', domain__debug(B));
ASSERT(ml_dec16(ml, offset + 1) === 2, 'nall should have 2 args');
if (!A || !B) return true;
if (indexA === indexB) {
TRACE(' - indexA==indexB so A=0 and eliminate constraint');
let oA = A;
A = domain_removeGtUnsafe(A, 0);
if (A !== oA) updateDomain(indexA, A, '`A !& A` means A must be zero');
ml_eliminate(ml, offset, SIZEOF_C_2);
return true;
}
if (domain_isZero(A) || domain_isZero(B)) {
TRACE(' - A=0 or B=0, eliminating constraint');
ml_eliminate(ml, offset, SIZEOF_C_2);
return true;
}
if (domain_hasNoZero(A)) {
TRACE(' - A>=1 so B must be 0');
let oB = B;
B = domain_removeGtUnsafe(B, 0);
if (B !== oB) updateDomain(indexB, B, 'nall[2] B');
ml_eliminate(ml, offset, SIZEOF_C_2);
return true;
}
if (domain_hasNoZero(B)) {
TRACE(' - B>=1 so A must be 0');
let oA = A;
A = domain_removeGtUnsafe(A, 0);
if (A !== oA) updateDomain(indexA, A, 'nall[2] A');
ml_eliminate(ml, offset, SIZEOF_C_2);
return true;
}
TRACE(' - min_nall_2 changed nothing');
return false;
}
function min_some(ml, offset) {
let offsetCount = offset + 1;
let argCount = ml_dec16(ml, offsetCount);
let offsetArgs = offset + SIZEOF_C;
let opSize = SIZEOF_C + argCount * 2;
TRACE(' = min_some', argCount, 'x');
TRACE(' - ml for this some:', ml.slice(offset, offset + opSize).join(' '));
TRACE(' -', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)));
TRACE(' -', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))));
if (!argCount) return setEmpty(-1, 'some without args is probably a bug');
if (argCount === 2) {
if (min_some_2(ml, offset)) return;
}
let countStart = argCount;
let lastIndex = -1;
let lastDomain;
// a some only ensures at least one of its arg solves to zero
for (let i = argCount - 1; i >= 0; --i) { // backwards because we're pruning dud indexes
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - loop i=', i, 'index=', index, 'domain=', domain__debug(domain));
if (!domain) return;
if (domain_isZero(domain) || lastIndex === index) {
// remove var from list
TRACE(lastIndex === index ? ' - removing redundant dupe var from some' : ' - domain contains no zero so remove var from this constraint');
// now
// - move all indexes bigger than the current back one position
// - compile the new count back in
// - compile a NOOP in the place of the last element
TRACE(' - moving further domains one space forward (from ', i + 1, ' / ', argCount, ')');
min_spliceArgSlow(ml, offsetArgs, argCount, i, false);
--argCount;
} else if (domain_hasNoZero(domain)) {
// remove constraint
TRACE(' - domain solved to nonzero so constraint is satisfied');
ml_eliminate(ml, offset, SIZEOF_C + 2 * countStart);
return;
} else {
// arg contains a 0 and is unsolved
TRACE(' - domain contains zero and is not solved so leave it alone');
lastIndex = index;
lastDomain = domain;
}
}
if (argCount === 0) {
TRACE(' - Some has no var left to be zero; rejecting problem');
// this is a bad state: all vars were removed from this constraint which
// means all of the args were zero and the constraint doesnt hold
return setEmpty(lastIndex, 'some; all of the args were zero');
} else if (argCount === 1) {
TRACE(' - Some has one var left; forcing it to nonzero');
// force set last index to nonzero and remove constraint. this should not
// reject (because then the var would have been removed in loop above)
// but do it "safe" anyways, just in case.
let domain = domain_removeValue(lastDomain, 0);
if (lastDomain !== domain && updateDomain(lastIndex, domain)) return;
ml_eliminate(ml, offset, SIZEOF_C + 2 * countStart);
} else if (countStart !== argCount) {
TRACE(' - recording new argcount and freeing up space');
ml_enc16(ml, offsetCount, argCount); // write new count
let free = (countStart - argCount) * 2;
ml_compileJumpSafe(ml, offset + opSize - free, free);
// note: still have to restart op because ml_jump may have clobbered the old end of the op with a new jump
} else {
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + opSize;
}
}
function min_isAll(ml, offset) {
let offsetCount = offset + 1;
let argCount = ml_dec16(ml, offsetCount);
let opSize = SIZEOF_C + 2 + argCount * 2;
let offsetArgs = offset + SIZEOF_C;
let offsetR = offset + opSize - 2;
let indexR = readIndex(ml, offsetR);
let R = getDomainFast(indexR);
TRACE(' = min_isAll', argCount, 'x');
TRACE(' - ml for this isAll (' + opSize + 'b):', ml.slice(offset, offset + opSize).join(' '));
TRACE(' -', indexR, '= all?(', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)), ')');
TRACE(' -', domain__debug(R), '= all?(', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))), ')');
if (!R) return;
if (domain_isZero(R)) {
TRACE(' - R is 0 so morph to nall and revisit');
// compile to nall and revisit
ml_enc8(ml, offset, ML_NALL);
ml_compileJumpSafe(ml, offset + opSize - 2, 2); // difference between nall with R=0 and an isAll is the result var (16bit)
return;
}
if (domain_hasNoZero(R)) {
TRACE(' - R is non-zero so remove zero from all args and eliminate constraint');
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
if (!domain) return;
let newDomain = domain_removeValue(domain, 0);
if (newDomain !== domain && updateDomain(index, newDomain)) return;
}
ml_eliminate(ml, offset, opSize);
return;
}
// R is unresolved. check whether R can be determined
ASSERT(domain_min(R) === 0 && domain_max(R) > 0, 'R is unresolved here', R);
let allNonZero = true;
let someAreZero = false;
let someNonZero = false;
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
// reflect isAll,
// R=0 when at least one arg is zero
// R>0 when all args have no zero
if (domain_isZero(domain)) {
TRACE(' - found a zero, breaking loop because R=0');
someAreZero = true;
break; // this permanently sets R to 0; no need to loop further
} else if (domain_min(domain) === 0) {
// arg has zero and non-zero values so R (at least) cant be set to 1 yet
allNonZero = false;
} else {
someNonZero = true;
}
}
if (someAreZero) {
TRACE(' - At least one arg was zero so R=0 and constraint can be removed');
let oR = R;
R = domain_removeGtUnsafe(R, 0);
if (R !== oR) updateDomain(indexR, R);
ml_eliminate(ml, offset, opSize);
return;
}
if (allNonZero) {
TRACE(' - No arg had zero so R=1 and constraint can be removed');
let oR = R;
R = domain_removeValue(R, 0);
if (R !== oR) updateDomain(indexR, R);
ml_eliminate(ml, offset, opSize);
return;
}
// remove all non-zero values from the list. this reduces their connection count and simplifies this isall
if (someNonZero) {
let removed = 0;
for (let i = argCount - 1; i >= 0; --i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - checking if index', index, '(domain', domain__debug(domain), ') contains no zero so we can remove it from this isall');
if (domain_hasNoZero(domain)) {
// now
// - move all indexes bigger than the current back one position
// - compile the new count back in
// - compile a NOOP in the place of the last element
TRACE(' - moving further domains one space forward (from ', i + 1, ' / ', argCount, ')');
min_spliceArgSlow(ml, offsetArgs, argCount, i, true);
--argCount;
++removed;
}
}
ml_enc16(ml, offset + 1, argCount);
// now "blank out" the space of eliminated constants, they should be at the end of the op
let newOpSize = opSize - removed * 2;
ml_compileJumpSafe(ml, offset + newOpSize, opSize - newOpSize);
TRACE(' - Removed', removed, 'non-zero args from unsolved isall, has', argCount, 'left');
TRACE(' - ml for this sum now:', ml.slice(offset, offset + opSize).join(' '));
if (argCount === 1) _min_isAllMorphToXnor(ml, offset, argCount, offsetArgs, indexR);
return;
}
if (argCount === 1) return _min_isAllMorphToXnor(ml, offset, argCount, offsetArgs, indexR);
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + opSize;
}
function _min_isAllMorphToXnor(ml, offset, argCount, offsetArgs, indexR) {
// while this usually only happens when eliminating non-zeroes, there may be an artifact where a script
// generated an isall with just one arg. kind of silly but whatever, right.
TRACE(' - Only one arg remaining; morphing to a XNOR');
ASSERT(argCount > 0, 'isall must have at least one arg in order to have enough space for the xnor morph');
let index = readIndex(ml, offsetArgs);
ml_cr2c2(ml, offset, argCount, ML_XNOR, indexR, index);
varChanged = true; // the xnor may need optimization
}
function min_isNall(ml, offset) {
let offsetCount = offset + 1;
let argCount = ml_dec16(ml, offsetCount);
let opSize = SIZEOF_C + argCount * 2 + 2;
let offsetArgs = offset + SIZEOF_C;
let offsetR = offset + opSize - 2;
let indexR = readIndex(ml, offsetR);
let R = getDomainFast(indexR);
TRACE(' = min_isNall', argCount, 'x');
TRACE(' - ml for this isNall:', ml.slice(offset, offset + opSize).join(' '));
TRACE(' -', indexR, '= nall?(', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)), ')');
TRACE(' -', domain__debug(R), '= nall?(', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))), ')');
if (!R) return;
if (domain_isZero(R)) {
TRACE(' - R=0 so; !nall so; all so; we must remove zero from all args and eliminate constraint');
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
if (!domain) return;
let newDomain = domain_removeValue(domain, 0);
if (newDomain !== domain && updateDomain(index, newDomain)) return;
}
ml_eliminate(ml, offset, opSize);
return;
}
if (domain_hasNoZero(R)) {
TRACE(' - R>0 so; nall. just morph and revisit');
ml_enc8(ml, offset, ML_NALL);
ml_compileJumpSafe(ml, offset + opSize - 2, 2); // difference between nall and isNall is the result var (16bit)
return;
}
// R is unresolved. check whether R can be determined
ASSERT(domain_min(R) === 0 && domain_max(R) > 0, 'R is unresolved here', R);
let allNonZero = true;
let someAreZero = false;
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
// reflect isNall,
// R=0 when all args have no zero
// R>0 when at least one arg is zero
if (domain_isZero(domain)) {
TRACE(' - found a zero, breaking loop because R=0');
someAreZero = true;
break; // this permanently sets R to 0; no need to loop further
} else if (domain_min(domain) === 0) {
// arg has zero and non-zero values so R (at least) cant be set to 1 yet
allNonZero = false;
}
}
if (someAreZero) {
TRACE(' - At least one arg was zero so R>=1 and constraint can be removed');
let oR = R;
R = domain_removeValue(R, 0);
if (R !== oR) updateDomain(indexR, R, 'isnall, R>=1 because at least one var was zero');
ml_eliminate(ml, offset, opSize);
} else if (allNonZero) {
TRACE(' - No arg had a zero so R=0 and constraint can be removed');
let oR = R;
R = domain_removeGtUnsafe(R, 0);
if (R !== oR) updateDomain(indexR, R);
ml_eliminate(ml, offset, opSize);
} else {
// TODO: prune all args here that are nonzero? is that worth it?
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + opSize;
}
}
function min_isSome(ml, offset) {
let offsetCount = offset + 1;
let argCount = ml_dec16(ml, offsetCount);
let opSize = SIZEOF_C + argCount * 2 + 2;
let offsetArgs = offset + SIZEOF_C;
let offsetR = offset + opSize - 2;
let indexR = readIndex(ml, offsetR);
let R = getDomainFast(indexR);
TRACE(' = min_isSome', argCount, 'x');
TRACE(' - ml for this isSome:', ml.slice(offset, offset + opSize).join(' '));
TRACE(' -', indexR, '= some?(', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)), ')');
TRACE(' -', domain__debug(R), '= some?(', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))), ')');
if (!R) return;
if (domain_isZero(R)) {
TRACE(' - R=0 so; !some so; none so; we must force zero to all args and eliminate constraint');
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
if (!domain) return;
let newDomain = domain_removeGtUnsafe(domain, 0);
if (newDomain !== domain && updateDomain(index, newDomain)) return;
}
ml_eliminate(ml, offset, opSize);
return;
}
if (domain_hasNoZero(R)) {
TRACE(' - R>0 so; some. just morph and revisit');
ml_enc8(ml, offset, ML_SOME);
ml_compileJumpSafe(ml, offset + opSize - 2, 2); // difference between some and isSome is the result var (16bit)
return;
}
// R is unresolved. check whether R can be determined
ASSERT(domain_min(R) === 0 && domain_max(R) > 0, 'R is unresolved here', R);
let someNonZero = false;
let allZero = true;
let someZero = false;
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
// reflect isSome,
// R=0 when all args are zero (already checked above)
// R>0 when at least one arg is nonzero
if (domain_hasNoZero(domain)) {
TRACE(' - found a nonzero, breaking loop because R>0');
someNonZero = true;
break; // this permanently sets R to 0; no need to loop further
} else if (domain_isZero(domain)) {
someZero = true;
} else {
allZero = false;
}
}
if (someNonZero) {
TRACE(' - At least one arg was zero so R>=1 and constraint can be removed');
let oR = R;
R = domain_removeValue(R, 0);
if (R !== oR) updateDomain(indexR, R, 'issome, R>=1 because at least one var was nonzero');
ml_eliminate(ml, offset, opSize);
} else if (allZero) {
TRACE(' - All vars were zero so R=0 and constraint can be removed');
let oR = R;
R = domain_removeGtUnsafe(R, 0);
if (R !== oR) updateDomain(indexR, R, 'issome, R>=1 because all vars were zero');
ml_eliminate(ml, offset, opSize);
} else if (someZero) {
TRACE(' - Some vars were zero, removing them from the args');
// force constants to the end
ml_heapSort16bitInline(ml, pc + SIZEOF_C, argCount);
// we know
// - these args do not contain non-zero args
// - all constants are moved to the back
// - there is at least one constant 0
// - not all args were 0
// so we can move back the result var
let argOffset = offsetArgs + argCount * 2 - 2;
TRACE(' - offset:', offset, ', argCount:', argCount, ', args offset:', offsetArgs, ', first arg domain:', domain__debug(getDomain(readIndex(ml, offsetArgs))), ', last arg offset:', argOffset, ', last domain:', domain__debug(getDomain(readIndex(ml, argOffset))));
TRACE(' - op before:', ml__debug(ml, offset, 1, problem));
ASSERT(domain_isZero(getDomain(readIndex(ml, argOffset))), 'at least the last arg should be zero', domain__debug(getDomain(readIndex(ml, argOffset))));
ASSERT(!domain_isZero(getDomain(readIndex(ml, offsetArgs))), 'the first arg should not be zero', domain__debug(getDomain(readIndex(ml, offsetArgs))));
// search for the first non-zero arg
let newArgCount = argCount;
while (domain_isZero(getDomain(readIndex(ml, argOffset)))) {
argOffset -= 2;
--newArgCount;
}
TRACE(' - last non-zero arg is arg number', newArgCount, 'at', argOffset, ', index:', readIndex(ml, argOffset), ', domain:', domain__debug(getDomain(readIndex(ml, argOffset))));
if (newArgCount === 1) {
TRACE(' - there is one arg left, morph to XNOR');
TRACE_MORPH('R = some?(A 0 0 ..)', 'R !^ A');
let indexA = readIndex(ml, offsetArgs);
ml_cr2c2(ml, offset, argCount, ML_XNOR, indexR, indexA);
} else {
TRACE(' - moving R to the first zero arg at offset', argOffset + 2, 'and compiling a jump for the rest');
// copy the result var over the first zero arg
ml_enc16(ml, offset + 1, newArgCount);
ml_enc16(ml, argOffset + 2, indexR);
ml_compileJumpSafe(ml, argOffset + 4, (argCount - newArgCount) * 2);
ASSERT(ml_validateSkeleton(ml, 'min_isSome; pruning zeroes'));
}
TRACE(' - op after:', ml__debug(ml, offset, 1, problem));
} else {
// TODO: prune all args here that are zero? is that worth it?
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + opSize;
}
}
function min_isNone(ml, offset) {
let offsetCount = offset + 1;
let argCount = ml_dec16(ml, offsetCount);
let opSize = SIZEOF_C + argCount * 2 + 2;
let offsetArgs = offset + SIZEOF_C;
let offsetR = offset + opSize - 2;
let indexR = readIndex(ml, offsetR);
let R = getDomainFast(indexR);
TRACE(' = min_isNone', argCount, 'x');
TRACE(' - ml for this isNone:', ml.slice(offset, offset + opSize).join(' '));
TRACE(' -', indexR, '= none?(', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)), ')');
TRACE(' -', domain__debug(R), '= none?(', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))), ')');
if (!R) return;
if (domain_hasNoZero(R)) {
TRACE(' - R>=1 so set all args to zero and eliminate');
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
if (!domain) return;
let newDomain = domain_removeGtUnsafe(domain, 0);
if (newDomain !== domain && updateDomain(index, newDomain)) return;
}
ml_eliminate(ml, offset, opSize);
return;
}
if (domain_isZero(R)) {
TRACE(' - R=0 so this is a SOME. just morph and revisit');
TRACE_MORPH('0 = none?(A B C ...)', 'some(A B C ...)');
ml_enc8(ml, offset, ML_SOME);
ml_compileJumpSafe(ml, offset + opSize - 2, 2); // difference between some and isNone is the result var (16bit)
return;
}
// R has a zero or is zero, determine whether there is any nonzero arg, or whether they are all zero
let nonZero = false;
let allZero = true;
for (let i = 0; i < argCount; ++i) {
let index = readIndex(ml, offsetArgs + i * 2);
let domain = getDomainFast(index);
TRACE(' - index=', index, 'dom=', domain__debug(domain));
// reflect isNone,
// R=0 when at least one arg is nonzero
// R>0 when all args are zero
if (domain_hasNoZero(domain)) {
nonZero = true;
break;
}
if (!domain_isZero(domain)) {
allZero = false;
}
}
if (nonZero) {
TRACE(' - at least one arg had no zero so R=0, eliminate constraint');
let oR = R;
R = domain_removeGtUnsafe(R, 0);
if (R !== oR) updateDomain(indexR, R, 'isnone R=0 because an arg had no zero');
ml_eliminate(ml, offset, opSize);
} else if (allZero) {
TRACE(' - isnone, all args are 0 so R>=1, remove constraint');
let oR = R;
R = domain_removeValue(R, 0);
if (R !== oR) updateDomain(indexR, R, 'isnone R>=1 because all args were zero');
ml_eliminate(ml, offset, opSize);
} else {
// TODO: prune all args here that are zero? is that worth it?
TRACE(' - not only jumps...');
onlyJumps = false;
pc = offset + SIZEOF_C + argCount * 2 + 2;
}
}
function min_diff(ml, offset) {
let argCount = ml_dec16(ml, offset + 1);
let offsetArgs = offset + SIZEOF_C;
let opSize = SIZEOF_C + argCount * 2;
TRACE(' = min_diff', argCount, 'x');
TRACE(' - ml for this diff:', ml.slice(offset, offset + opSize).join(' '));
TRACE(' - indexes:', Array.from(Array(argCount)).map((n, i) => readIndex(ml, offsetArgs + i * 2)).join(', '));
TRACE(' - domains:', Array.from(Array(argCount)).map((n, i) => domain__debug(getDomainFast(readIndex(ml, offsetArgs + i * 2)))).join(', '));
ASSERT(argCount, 'should have at least one arg or be eliminated');
if (!argCount) return setEmpty(-1, 'diff without args is probably a bug');
let countStart = argCount;
// a diff is basically a pyramid of neq's; one for each unique pair of the set
// we loop back to front because we're splicing out vars while looping
for (let i = argCount - 1; i >= 0; --i) {
let indexA = readIndex(ml, offsetArgs + i * 2);
let A = getDomainFast(indexA);
TRACE(' - loop i=', i, 'index=', indexA, 'domain=', domain__debug(A));
if (!A) return;
let v = domain_getValue(A);
if (v >= 0) {
TRACE(' - solved, so removing', v, 'from all other domains and index', indexA, 'from the constraint');
for (let j = 0; j < argCount; ++j) { // gotta loop through all args. args wont be removed in this loop.
if (j !== i) {
let indexB = readIndex(ml, offsetArgs + j * 2);
let oB = getDomainFast(indexB);
TRACE(' - loop j=', j, 'index=', indexB, 'domain=', domain__debug(oB));
if (indexA === indexB) return updateDomain(indexA, domain_createEmpty(), 'diff had this var twice, x!=x is a falsum'); // edge case
let B = domain_removeValue(oB, v);
if (B !== oB && updateDomain(indexB, B, 'diff arg')) return;
}
}
// so none of the other args have v and none of them ended up empty
// now
// - move all indexes bigger than the current back one position
// - compile the new count back in
// - compile a NOOP in the place of the last element
TRACE(' - moving further domains one space forward (from ', i + 1, ' / ', argCount, ')', i + 1 < argCount);
min_spliceArgSlow(ml, offsetArgs, argCount, i, true); // move R as well
--argCount;
}
}
if (argCount <= 1) {
TRACE(' - Count is', argCount, '; eliminating constraint');
ASSERT(argCount >= 0, 'cant be negative');
ml_eliminate(ml, offset, opSize);
} else if (argCount !== countStart) {
TRACE(' - recompiling new count (', argCount, ')');
ml_enc16(ml, offset + 1, argCount);
TRACE(' - compiling noop into empty spots'); // this hasnt happened yet
ml_compileJumpSafe(ml, offsetArgs + argCount * 2, (countStart - argCount) * 2);
// need to restart op because the skip may have clobbered the next op offset
} else if (argCount === 2 && min_diff_2(ml, offset)) {
// do nothing. min_diff_2 has already done something.
} else {
TRACE(' - not only jumps...', opSize);
onlyJumps = false;
pc = offset + opSize;
}
}
function min_sum_2(ml, sumOffset) {