fdp
Version:
Finite Domain Problem reduction system
1,332 lines (1,172 loc) • 53.8 kB
JavaScript
import {
ASSERT,
getTerm,
TRACE,
TRACE_SILENT,
THROW,
} from '../../fdlib/src/helpers';
import {
domain__debug,
} from '../../fdlib/src/domain';
// BODY_START
let ml_opcodeCounter = 0;
// note: all ops accept vars and literals
// - a var is signified by a V
// - an 8bit literal signified by 8
// - a 16bit literal signified by F
const ML_START = ml_opcodeCounter++;
const ML_ALL = ml_opcodeCounter++; // & all()
const ML_DIFF = ml_opcodeCounter++; // != diff()
const ML_IMP = ml_opcodeCounter++; // -> (logical implication)
const ML_LT = ml_opcodeCounter++; // <
const ML_LTE = ml_opcodeCounter++; // <=
const ML_NALL = ml_opcodeCounter++; // !& nall()
const ML_NIMP = ml_opcodeCounter++; // !(->)
const ML_NONE = ml_opcodeCounter++; // ==0 none()
const ML_SAME = ml_opcodeCounter++; // == same()
const ML_SOME = ml_opcodeCounter++; // | some()
const ML_XNOR = ml_opcodeCounter++; // !^ xnor()
const ML_XOR = ml_opcodeCounter++; // ^ xor()
const ML_ISALL = ml_opcodeCounter++;
const ML_ISDIFF = ml_opcodeCounter++;
const ML_ISLT = ml_opcodeCounter++;
const ML_ISLTE = ml_opcodeCounter++;
const ML_ISNALL = ml_opcodeCounter++;
const ML_ISNONE = ml_opcodeCounter++;
const ML_ISSAME = ml_opcodeCounter++;
const ML_ISSOME = ml_opcodeCounter++;
const ML_SUM = ml_opcodeCounter++;
const ML_PRODUCT = ml_opcodeCounter++;
const ML_MINUS = ml_opcodeCounter++;
const ML_DIV = ml_opcodeCounter++;
const ML_NOLEAF = ml_opcodeCounter++;
const ML_NOBOOL = ml_opcodeCounter++;
const ML_JMP = ml_opcodeCounter++;
const ML_JMP32 = ml_opcodeCounter++;
const ML_NOOP = ml_opcodeCounter++;
const ML_NOOP2 = ml_opcodeCounter++;
const ML_NOOP3 = ml_opcodeCounter++;
const ML_NOOP4 = ml_opcodeCounter++;
const ML_STOP = 0xff;
ASSERT(ml_opcodeCounter < 0xff, 'All opcodes are 8bit');
ASSERT(ML_START === 0);
ASSERT(ML_STOP === 0xff);
const SIZEOF_V = 1 + 2; // 16bit
const SIZEOF_W = 1 + 4; // 32bit
const SIZEOF_VV = 1 + 2 + 2;
const SIZEOF_VVV = 1 + 2 + 2 + 2;
const SIZEOF_C = 1 + 2; // + 2*count
const SIZEOF_C_2 = SIZEOF_C + 2 * 2; // fixed 2 var without result
const SIZEOF_CR_2 = SIZEOF_C + 2 * 2 + 2; // fixed 2 var with result
const OFFSET_C_A = SIZEOF_C;
const OFFSET_C_B = SIZEOF_C + 2;
const OFFSET_C_C = SIZEOF_C + 4;
const OFFSET_C_R = OFFSET_C_C;
let ml_typeCounter = 0;
const ML_NO_ARGS = ++ml_typeCounter;
const ML_V = ++ml_typeCounter;
const ML_W = ++ml_typeCounter;
const ML_VV = ++ml_typeCounter;
const ML_VVV = ++ml_typeCounter;
const ML_C = ++ml_typeCounter;
const ML_C_2 = ++ml_typeCounter;
const ML_CR = ++ml_typeCounter;
const ML_C8R = ++ml_typeCounter;
function ml_sizeof(ml, offset, op) {
switch (op) {
case ML_IMP:
case ML_LT:
case ML_LTE:
case ML_NIMP:
case ML_XOR:
ASSERT(ml_dec16(ml, offset + 1) === 2);
return SIZEOF_C_2; // always
case ML_START:
return 1;
case ML_ISLT:
case ML_ISLTE:
case ML_MINUS:
case ML_DIV:
return SIZEOF_VVV;
case ML_ALL:
case ML_DIFF:
case ML_NALL:
case ML_NONE:
case ML_SAME:
case ML_SOME:
case ML_XNOR:
if (ml && offset >= 0) return SIZEOF_C + _dec16(ml, offset + 1) * 2;
return -1;
case ML_ISALL:
case ML_ISDIFF:
case ML_ISNALL:
case ML_ISNONE:
case ML_ISSAME:
case ML_ISSOME:
if (ml && offset >= 0) return SIZEOF_C + _dec16(ml, offset + 1) * 2 + 2;
ASSERT(false, 'dont allow this');
return -1;
case ML_SUM:
case ML_PRODUCT:
if (ml && offset >= 0) return SIZEOF_C + _dec16(ml, offset + 1) * 2 + 2;
ASSERT(false, 'dont allow this');
return -1;
case ML_NOBOOL:
case ML_NOLEAF:
return SIZEOF_V;
case ML_JMP:
return SIZEOF_V + _dec16(ml, offset + 1);
case ML_JMP32:
return SIZEOF_W + _dec32(ml, offset + 1);
case ML_NOOP:
return 1;
case ML_NOOP2:
return 2;
case ML_NOOP3:
return 3;
case ML_NOOP4:
return 4;
case ML_STOP:
return 1;
default:
getTerm().log('(ml) unknown op', op, ' at', offset);
TRACE('(ml_sizeof) unknown op: ' + ml[offset], ' at', offset);
THROW('(ml_sizeof) unknown op: ' + ml[offset], ' at', offset);
}
}
function _dec8(ml, pc) {
return ml[pc];
}
function ml_dec8(ml, pc) {
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array');
ASSERT(typeof pc === 'number' && pc >= 0 && pc < ml.length, 'Invalid or OOB', pc, '>=', ml.length);
let num = _dec8(ml, pc);
TRACE_SILENT(' . dec8pc decoding', num, 'from', pc);
return num;
}
function _dec16(ml, pc) {
return (ml[pc++] << 8) | ml[pc];
}
function ml_dec16(ml, pc) {
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array');
ASSERT(typeof pc === 'number' && pc >= 0 && pc < ml.length, 'Invalid or OOB', pc, '>=', ml.length);
let n = _dec16(ml, pc);
TRACE_SILENT(' . dec16pc decoding', ml[pc] << 8, 'from', pc, 'and', ml[pc + 1], 'from', pc + 1, '-->', n);
return n;
}
function _dec32(ml, pc) {
return (ml[pc++] << 24) | (ml[pc++] << 16) | (ml[pc++] << 8) | ml[pc];
}
function ml_dec32(ml, pc) {
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array');
ASSERT(typeof pc === 'number' && pc >= 0 && pc < ml.length, 'Invalid or OOB', pc, '>=', ml.length);
let n = _dec32(ml, pc);
TRACE_SILENT(' . dec32pc decoding', ml[pc], ml[pc + 1], ml[pc + 2], ml[pc + 3], '( x' + ml[pc].toString(16) + ml[pc + 1].toString(16) + ml[pc + 2].toString(16) + ml[pc + 3].toString(16), ') from', pc, '-->', n);
return n;
}
function ml_enc8(ml, pc, num) {
TRACE_SILENT(' . enc8(' + pc + ', ' + num + '/x' + (num && num.toString(16)) + ') at', pc, ' ');
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array');
ASSERT(typeof pc === 'number' && pc >= 0 && pc < ml.length, 'Invalid or OOB', pc, '>=', ml.length);
ASSERT(typeof num === 'number', 'Encoding numbers', num);
ASSERT(num >= 0 && num <= 0xff, 'Only encode 8bit values', num, '0x' + num.toString(16));
ASSERT(num >= 0, 'only expecting non-negative nums', num);
ml[pc] = num;
}
function ml_enc16(ml, pc, num) {
TRACE_SILENT(' - enc16(' + pc + ', ' + num + '/x' + num.toString(16) + ')', (num >> 8) & 0xff, 'at', pc, 'and', num & 0xff, 'at', pc + 1);
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array');
ASSERT(typeof pc === 'number' && pc >= 0 && pc < ml.length, 'Invalid or OOB', pc, '>=', ml.length);
ASSERT(typeof num === 'number', 'Encoding numbers');
ASSERT(num <= 0xffff, 'implement 32bit index support if this breaks', num);
ASSERT(num >= 0, 'only expecting non-negative nums', num);
ml[pc++] = (num >> 8) & 0xff;
ml[pc] = num & 0xff;
}
function ml_enc32(ml, pc, num) {
TRACE_SILENT(' - enc32(' + pc + ', ' + num + '/x' + num.toString(16) + ')', ml[pc], ml[pc + 1], ml[pc + 2], ml[pc + 3], '( x' + ml[pc].toString(16) + ml[pc + 1].toString(16) + ml[pc + 2].toString(16) + ml[pc + 3].toString(16), ') at', pc + 1);
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array');
ASSERT(typeof pc === 'number' && pc >= 0 && pc < ml.length, 'Invalid or OOB', pc, '>=', ml.length);
ASSERT(typeof num === 'number', 'Encoding numbers');
ASSERT(num <= 0xffffffff, 'implement 64bit index support if this breaks', num);
ASSERT(num >= 0, 'only expecting non-negative nums', num);
ml[pc++] = (num >> 24) & 0xff;
ml[pc++] = (num >> 16) & 0xff;
ml[pc++] = (num >> 8) & 0xff;
ml[pc] = num & 0xff;
}
function ml_eliminate(ml, offset, sizeof) {
ASSERT(ml instanceof Uint8Array, 'ml should be Uint8Array', ml);
//ASSERT(ml_validateSkeleton(ml, 'ml_eliminate; before'));
TRACE(' - ml_eliminate: eliminating constraint at', offset, 'with size =', sizeof, ', ml=', ml.length < 50 ? ml.join(' ') : '<BIG>');
ASSERT(typeof offset === 'number' && offset >= 0 && offset < ml.length, 'valid offset required');
ASSERT(typeof sizeof === 'number' && sizeof >= 0, 'valid sizeof required');
ASSERT(sizeof === ml_getOpSizeSlow(ml, offset), 'sizeof should match size of op at offset', sizeof, ml_getOpSizeSlow(ml, offset), ml__debug(ml, offset, 1, undefined, true, true)); // maybe we should move to do this permanently "slow"
ml_compileJumpSafe(ml, offset, sizeof);
TRACE(' - after ml_eliminate:', ml.length < 50 ? ml.join(' ') : '<trunced>');
ASSERT(ml_validateSkeleton(ml, 'ml_eliminate; after'));
}
function ml_compileJumpAndConsolidate(ml, offset, len) {
TRACE(' - ml_jump: offset = ', offset, 'len = ', len);
switch (ml[offset + len]) {
case ML_NOOP:
TRACE(' - jmp target is another jmp (noop), merging them');
return ml_compileJumpAndConsolidate(ml, offset, len + 1);
case ML_NOOP2:
TRACE(' - jmp target is another jmp (noop2), merging them');
return ml_compileJumpAndConsolidate(ml, offset, len + 2);
case ML_NOOP3:
TRACE(' - jmp target is another jmp (noop3), merging them');
return ml_compileJumpAndConsolidate(ml, offset, len + 3);
case ML_NOOP4:
TRACE(' - jmp target is another jmp (noop4), merging them');
return ml_compileJumpAndConsolidate(ml, offset, len + 4);
case ML_JMP:
let jmplen = ml_dec16(ml, offset + len + 1);
ASSERT(jmplen > 0, 'dont think zero is a valid jmp len');
ASSERT(jmplen <= 0xffff, 'oob');
TRACE(' - jmp target is another jmp (len =', SIZEOF_V + jmplen, '), merging them');
return ml_compileJumpAndConsolidate(ml, offset, len + SIZEOF_V + jmplen);
case ML_JMP32:
let jmplen32 = ml_dec32(ml, offset + len + 1);
ASSERT(jmplen32 > 0, 'dont think zero is a valid jmp len');
ASSERT(jmplen32 <= 0xffffffff, 'oob');
TRACE(' - jmp target is a jmp32 (len =', SIZEOF_W + jmplen32, '), merging them');
return ml_compileJumpAndConsolidate(ml, offset, len + SIZEOF_W + jmplen32);
}
ml_compileJumpSafe(ml, offset, len);
}
function ml_compileJumpSafe(ml, offset, len) {
switch (len) {
case 0:
return THROW('this is a bug');
case 1:
TRACE(' - compiling a NOOP');
return ml_enc8(ml, offset, ML_NOOP);
case 2:
TRACE(' - compiling a NOOP2');
return ml_enc8(ml, offset, ML_NOOP2);
case 3:
TRACE(' - compiling a NOOP3');
return ml_enc8(ml, offset, ML_NOOP3);
case 4:
TRACE(' - compiling a NOOP4');
return ml_enc8(ml, offset, ML_NOOP4);
default:
if (len < 0xffff) {
TRACE(' - compiling a ML_JMP of', len, '(compiles', len - SIZEOF_V, 'because SIZEOF_V=', SIZEOF_V, ')');
ml_enc8(ml, offset, ML_JMP);
ml_enc16(ml, offset + 1, len - SIZEOF_V);
} else {
TRACE(' - compiling a ML_JMP32 of', len, '(compiles', len - SIZEOF_W, 'because SIZEOF_W=', SIZEOF_W, ')');
ml_enc8(ml, offset, ML_JMP32);
ml_enc32(ml, offset + 1, len - SIZEOF_W);
}
}
//ASSERT(ml_validateSkeleton(ml, 'ml_jump; after'));
}
function ml_pump(ml, offset, from, to, len) {
TRACE(' - pumping from', offset + from, 'to', offset + to, '(len=', len, ')');
let fromOffset = offset + from;
let toOffset = offset + to;
for (let i = 0; i < len; ++i) {
TRACE(' - pump', fromOffset, toOffset, '(1)');
ml[fromOffset++] = ml[toOffset++];
}
}
function ml_countConstraints(ml) {
let pc = 0;
let constraints = 0;
while (pc < ml.length) {
let pcStart = pc;
let op = ml[pc];
switch (op) {
case ML_START:
if (pc !== 0) return THROW('mlConstraints: zero op @', pcStart, 'Uint8Array(' + ml.toString('hex').replace(/(..)/g, '$1 ') + ')');
++pc;
break;
case ML_STOP:
return constraints;
case ML_NOOP:
++pc;
break;
case ML_NOOP2:
pc += 2;
break;
case ML_NOOP3:
pc += 3;
break;
case ML_NOOP4:
pc += 4;
break;
case ML_JMP:
pc += SIZEOF_V + _dec16(ml, pc + 1);
break;
case ML_JMP32:
pc += SIZEOF_W + _dec32(ml, pc + 1);
break;
default:
let size = ml_sizeof(ml, pc, op); // throws if op is unknown
++constraints;
pc += size;
}
}
THROW('ML OOB');
}
function ml_hasConstraint(ml) {
// technically this should be cheap; either the first
// op is a constraint or it's a jump directly to stop.
// (all jumps should be consolidated)
let pc = 0;
while (pc < ml.length) {
switch (ml[pc]) {
case ML_START:
if (pc !== 0) return ml_throw('oops');
++pc;
break;
case ML_STOP:
return false;
case ML_NOOP:
++pc;
break;
case ML_NOOP2:
pc += 2;
break;
case ML_NOOP3:
pc += 3;
break;
case ML_NOOP4:
pc += 4;
break;
case ML_JMP:
pc += SIZEOF_V + _dec16(ml, pc + 1);
break;
case ML_JMP32:
pc += SIZEOF_W + _dec32(ml, pc + 1);
break;
default:
return true;
}
}
THROW('ML OOB');
}
function ml_c2vv(ml, offset, argCount, opCode, indexA, indexB) {
// "count without result" (diff, some, nall, etc)
TRACE(' -| ml_c2vv | from', offset, ', argCount=', argCount, 'to op', ml__opName(opCode), ', args:', indexA, indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) >= SIZEOF_VV, 'the c2 should fit the existing space entirely');
ASSERT(ml_dec16(ml, offset + 1) === argCount, 'argcount should match');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
let oldLen = SIZEOF_C + argCount * 2;
if (SIZEOF_VV < oldLen) ml_compileJumpSafe(ml, offset + SIZEOF_VV, oldLen - SIZEOF_VV);
ASSERT(ml_validateSkeleton(ml, 'ml_c2vv'));
}
function ml_c2c2(ml, offset, argCount, opCode, indexA, indexB) {
// "count without result" (diff, some, nall, etc) to same count type with 2 args without result
TRACE(' -| ml_c2c2 | from', offset, ', argCount=', argCount, 'to op', ml__opName(opCode), ', args:', indexA, indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) >= SIZEOF_C_2, 'the c2 should fit the existing space entirely');
ASSERT(ml_dec16(ml, offset + 1) === argCount, 'argcount should match');
ASSERT(argCount > 1, 'this fails with count<2 because theres not enough space');
//ASSERT(ml_validateSkeleton(ml, 'ml_c2c2-before'));
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, 2);
ml_enc16(ml, offset + OFFSET_C_A, indexA);
ml_enc16(ml, offset + OFFSET_C_B, indexB);
let oldLen = SIZEOF_C + argCount * 2;
if (SIZEOF_C_2 < oldLen) ml_compileJumpSafe(ml, offset + SIZEOF_C_2, oldLen - SIZEOF_C_2);
ASSERT(ml_validateSkeleton(ml, 'ml_c2c2'));
}
function ml_cx2cx(ml, offset, argCount, opCode, args) {
TRACE(' -| ml_cx2cx | from', offset, 'was argCount=', argCount, 'to op', ml__opName(opCode), 'with args', args, ', new size should be', SIZEOF_C + args.length * 2);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof argCount === 'number' && argCount > 0 && argCount < ml.length, 'valid argCount', argCount);
ASSERT(args instanceof Array, 'args is list of indexes', args);
ASSERT(argCount === args.length, 'this function excepts to morph one count op into another count op of the same size', argCount, args.length, args);
args.sort((a, b) => a - b); // compile args sorted
let opSize = SIZEOF_C + argCount * 2;
ASSERT((argCount === args.length) === (ml_getOpSizeSlow(ml, offset) === opSize), 'if same argcount then same size');
ASSERT(ml_getOpSizeSlow(ml, offset) === opSize, 'the should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, argCount);
for (let i = 0; i < argCount; ++i) {
ml_enc16(ml, offset + SIZEOF_C + i * 2, args[i]);
}
ASSERT(ml_validateSkeleton(ml, 'ml_cx2cx'));
}
function ml_any2c(ml, offset, oldSizeof, opCode, args) {
TRACE(' -| ml_any2c | from', offset, 'was len=', oldSizeof, 'to op', ml__opName(opCode), 'with args', args, ', new size should be', SIZEOF_C + args.length * 2);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof oldSizeof === 'number' && offset > 0 && offset < ml.length, 'valid oldSizeof', oldSizeof);
ASSERT(args instanceof Array, 'args is list of indexes', args);
let count = args.length;
let opSize = SIZEOF_C + count * 2;
ASSERT(ml_getOpSizeSlow(ml, offset) >= opSize, 'the c2 should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, count);
for (let i = 0; i < count; ++i) {
ml_enc16(ml, offset + SIZEOF_C + i * 2, args[i]);
}
if (opSize < oldSizeof) ml_compileJumpSafe(ml, offset + opSize, oldSizeof - opSize);
ASSERT(ml_validateSkeleton(ml, 'ml_any2c'));
}
function ml_any2cr(ml, offset, oldSizeof, opCode, args, indexR) {
TRACE(' -| ml_any2cr | from', offset, 'was len=', oldSizeof, 'to op', ml__opName(opCode), 'with args', args, ', indexR=', indexR, ', new size should be', SIZEOF_C + args.length * 2 + 2);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof oldSizeof === 'number' && offset > 0 && offset < ml.length, 'valid oldSizeof', oldSizeof);
ASSERT(args instanceof Array, 'args is list of indexes', args);
ASSERT(typeof indexR === 'number', 'valid indexR', indexR);
let count = args.length;
let opSize = SIZEOF_C + count * 2 + 2;
ASSERT(ml_getOpSizeSlow(ml, offset) >= opSize, 'the cr should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, count);
for (let i = 0; i < count; ++i) {
ml_enc16(ml, offset + SIZEOF_C + i * 2, args[i]);
}
ml_enc16(ml, offset + SIZEOF_C + count * 2, indexR);
ASSERT(opSize <= oldSizeof, 'should fit!');
if (opSize < oldSizeof) ml_compileJumpSafe(ml, offset + opSize, oldSizeof - opSize);
ASSERT(ml_validateSkeleton(ml, 'ml_any2cr'));
}
function ml_cr2vv(ml, offset, argCount, opCode, indexA, indexB) {
TRACE(' -| ml_cr2vv | from', offset, ', argCount=', argCount, 'to op', ml__opName(opCode), 'with args:', indexA, indexB);
ASSERT(argCount >= 1, 'if this is called for count ops with 0 args then we have a problem... a vv wont fit that');
// "count with result"
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) >= SIZEOF_VV, 'the vv should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
let oldLen = SIZEOF_C + argCount * 2 + 2;
if (SIZEOF_VV < oldLen) ml_compileJumpSafe(ml, offset + SIZEOF_VV, oldLen - SIZEOF_VV);
ASSERT(ml_validateSkeleton(ml, 'ml_cr2vv'));
}
function ml_cr2vvv(ml, offset, argCount, opCode, indexA, indexB, indexC) {
TRACE(' -| ml_cr2vvv | from', offset, ', argCount=', argCount, 'to op', ml__opName(opCode), 'with args:', indexA, indexB, indexC);
ASSERT(argCount >= 2, 'if this is called for count ops with 1 or 0 args then we have a problem... a vvv wont fit that');
// "count with result"
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(typeof indexC === 'number' && indexC >= 0, 'valid indexC', indexC);
ASSERT(ml_getOpSizeSlow(ml, offset) >= SIZEOF_VVV, 'the vvv should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
ml_enc16(ml, offset + 5, indexC);
let oldLen = SIZEOF_C + argCount * 2 + 2;
if (SIZEOF_VVV < oldLen) ml_compileJumpSafe(ml, offset + SIZEOF_VVV, oldLen - SIZEOF_VVV);
ASSERT(ml_validateSkeleton(ml, 'ml_cr2vvv'));
}
function ml_cr2cr2(ml, offset, argCount, opCode, indexA, indexB, indexC) {
// "count with result and any args to count with result with 2 args"
TRACE(' -| ml_cr2cr2 | from', offset, ', argCount=', argCount, 'to op', ml__opName(opCode), 'with args:', indexA, indexB, indexC);
ASSERT(argCount >= 2, 'if this is called for count ops with 1 or 0 args then we have a problem... a cr[' + argCount + '] wont fit that');
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(typeof indexC === 'number' && indexC >= 0, 'valid indexC', indexC);
ASSERT(ml_getOpSizeSlow(ml, offset) >= SIZEOF_CR_2, 'the cr2 should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, 2); // arg count
ml_enc16(ml, offset + OFFSET_C_A, indexA);
ml_enc16(ml, offset + OFFSET_C_B, indexB);
ml_enc16(ml, offset + OFFSET_C_C, indexC);
let oldLen = SIZEOF_C + argCount * 2 + 2;
if (SIZEOF_CR_2 < oldLen) ml_compileJumpSafe(ml, offset + SIZEOF_CR_2, oldLen - SIZEOF_CR_2);
ASSERT(ml_validateSkeleton(ml, 'ml_cr2cr2'));
}
function ml_cr2c2(ml, offset, argCount, opCode, indexA, indexB) {
// "count with result"
let oldArgCount = ml_dec16(ml, offset + 1);
TRACE(' -| ml_cr2c2 | from', offset, ', with argCount=', oldArgCount, ' and a result var, to a argCount=', argCount, ' without result, op', ml__opName(opCode), ', args:', indexA, indexB);
// count with result and any args to count with result and (exactly) 2 args
ASSERT(argCount >= 1, 'if this is called for count ops with 0 args then we have a problem... a c[' + argCount + '] wont fit that');
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) >= SIZEOF_C_2, 'the c2 should fit the existing space entirely');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, 2); // arg count
ml_enc16(ml, offset + OFFSET_C_A, indexA);
ml_enc16(ml, offset + OFFSET_C_B, indexB);
let oldLen = SIZEOF_C + oldArgCount * 2 + 2;
if (SIZEOF_C_2 < oldLen) ml_compileJumpSafe(ml, offset + SIZEOF_C_2, oldLen - SIZEOF_C_2);
ASSERT(ml_validateSkeleton(ml, 'ml_cr2c2'));
}
function ml_cr2c(ml, offset, oldArgCount, opCode, args) {
// "count with result to count"
// count with result and any args to count without result and any args
// not "any" because the number of new args can at most be only be one more than the old arg count
TRACE(' -| ml_cr2c | from', offset, ', with oldArgCount=', oldArgCount, ' and a result var, to a oldArgCount=', oldArgCount, ' without result, op', ml__opName(opCode), ', args:', args);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof oldArgCount === 'number', 'valid oldArgCount', oldArgCount);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(args instanceof Array && args.every(v => typeof v === 'number' && v >= 0));
ASSERT(oldArgCount + 1 >= args.length, 'cr can holds one index more than c so we can compile one more arg here', oldArgCount, '->', args.length);
let newArgCount = args.length;
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, newArgCount);
for (let i = 0; i < newArgCount; ++i) {
ml_enc16(ml, offset + SIZEOF_C + i * 2, args[i]);
}
let oldLen = SIZEOF_C + oldArgCount * 2 + 2;
let newLen = SIZEOF_C + newArgCount * 2;
if (newLen < oldLen) ml_compileJumpSafe(ml, offset + newLen, oldLen - newLen);
ASSERT(ml_validateSkeleton(ml, 'ml_cr2c'));
}
function ml_vv2vv(ml, offset, opCode, indexA, indexB) {
TRACE(' -| ml_vv2vv | from', offset, 'to op', ml__opName(opCode), ', index AB:', indexA, indexB);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) === SIZEOF_VV, 'the existing space should be a vv');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
ASSERT(ml_validateSkeleton(ml, 'ml_vv2vv'));
}
function ml_vvv2vv(ml, offset, opCode, indexA, indexB) {
TRACE(' -| ml_vvv2vv |', 'to op', ml__opName(opCode), ', args:', indexA, indexB);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) === SIZEOF_VVV, 'the existing space should be a vvv');
ASSERT(ml_getOpSizeSlow(ml, offset) > SIZEOF_VVV, 'the existing vvv should be larger than a vv');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
ml_compileJumpSafe(ml, offset + SIZEOF_VV, SIZEOF_VVV - SIZEOF_VV);
ASSERT(ml_validateSkeleton(ml, 'ml_vvv2vv'));
}
function ml_vvv2c2(ml, offset, opCode, indexA, indexB) {
TRACE(' -| ml_vvv2c2 |', 'to op', ml__opName(opCode), ', args:', indexA, indexB);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(ml_getOpSizeSlow(ml, offset) === SIZEOF_C_2, 'the existing space should be a vvv and that should be a c2');
ASSERT(SIZEOF_VVV === SIZEOF_C_2, 'need to check here if this changes');
// note: size(vvv) is same as size(c2)
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, 2);
ml_enc16(ml, offset + OFFSET_C_A, indexA);
ml_enc16(ml, offset + OFFSET_C_B, indexB);
ASSERT(ml_validateSkeleton(ml, 'ml_vvv2c2'));
}
function ml_vvv2vvv(ml, offset, opCode, indexA, indexB, indexR) {
TRACE(' -| cr_vvv2vvv |', 'to op', ml__opName(opCode), ', args:', indexA, indexB, indexR);
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset > 0 && offset < ml.length, 'valid offset', offset);
ASSERT(typeof opCode === 'number' && offset >= 0, 'valid opCode', opCode);
ASSERT(typeof indexA === 'number' && indexA >= 0, 'valid indexA', indexA);
ASSERT(typeof indexB === 'number' && indexB >= 0, 'valid indexB', indexB);
ASSERT(typeof indexR === 'number' && indexR >= 0, 'valid indexR', indexR);
ASSERT(ml_getOpSizeSlow(ml, offset) === SIZEOF_VVV, 'the existing space should be a vvv');
ml_enc8(ml, offset, opCode);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
ml_enc16(ml, offset + 5, indexR);
ASSERT(ml_validateSkeleton(ml, 'ml_vvv2vvv'));
}
function ml_walk(ml, offset, callback) {
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset >= 0 && offset < ml.length, 'offset should be valid and not oob');
ASSERT(typeof callback === 'function', 'callback should be callable');
let len = ml.length;
let op = ml[offset];
while (offset < len) {
op = ml[offset];
ASSERT(!(offset === 0 || op !== ML_START) ? ml_throw(ml, offset, 'should not see op=0 unless offset=0') : 1);
let sizeof = ml_sizeof(ml, offset, op);
ASSERT(sizeof > 0, 'ops should occupy space');
let r = callback(ml, offset, op, sizeof);
if (r !== undefined) return r;
offset += sizeof;
}
}
/**
* Walk the ml with a callback for each var encountered
*
* @param {Uint8Array} ml
* @param {number} offset
* @param {Function} callback Called as opCallback(ml, opoffset, optype, opcode, ...args) the actual `args` depend on the optype
*/
function ml_stream(ml, offset, callback) {
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset >= 0 && offset < ml.length, 'offset should be valid and not oob');
ASSERT(typeof callback === 'function', 'callback should be callable');
let r;
let len = ml.length;
let op = ml[offset];
while (offset < len) {
op = ml[offset];
ASSERT(offset === 0 || op !== ML_START, 'should not see op=0 unless offset=0', 'offset=', offset, 'ml=', ml);
let sizeof = 0;
switch (op) {
case ML_IMP:
case ML_LT:
case ML_LTE:
case ML_NIMP:
case ML_XOR:
r = callback(ml, offset, ML_C_2, op, ml_dec16(ml, offset + OFFSET_C_A), ml_dec16(ml, offset + OFFSET_C_B));
sizeof = SIZEOF_C_2;
break;
case ML_ISLT:
case ML_ISLTE:
case ML_MINUS:
case ML_DIV:
r = callback(ml, offset, ML_VVV, op, ml_dec16(ml, offset + 1), ml_dec16(ml, offset + 3), ml_dec16(ml, offset + 5));
sizeof = SIZEOF_CR_2;
break;
case ML_ALL:
case ML_DIFF:
case ML_NALL:
case ML_NONE:
case ML_SAME:
case ML_SOME:
case ML_XNOR:
r = callback(ml, offset, ML_C, op, ml_dec16(ml, offset + 1));
sizeof = SIZEOF_C + ml_dec16(ml, offset + 1) * 2;
break;
case ML_ISALL:
case ML_ISDIFF:
case ML_ISNALL:
case ML_ISNONE:
case ML_ISSAME:
case ML_ISSOME:
case ML_PRODUCT:
case ML_SUM:
r = callback(ml, offset, ML_CR, op, ml_dec16(ml, offset + 1), ml_dec16(ml, offset + SIZEOF_C + ml_dec16(ml, offset + 1) * 2));
sizeof = SIZEOF_C + ml_dec16(ml, offset + 1) * 2 + 2;
break;
case ML_NOBOOL:
case ML_NOLEAF:
r = callback(ml, offset, ML_V, op, ml_dec16(ml, offset + 1));
sizeof = SIZEOF_V;
break;
case ML_JMP:
r = callback(ml, offset, ML_V, op, ml_dec16(ml, offset + 1));
sizeof = SIZEOF_V + ml_dec16(ml, offset + 1);
break;
case ML_JMP32:
r = callback(ml, offset, ML_W, op, ml_dec32(ml, offset + 1));
sizeof = SIZEOF_W + ml_dec32(ml, offset + 1);
break;
case ML_NOOP2:
r = callback(ml, offset, ML_NO_ARGS, op);
sizeof = 2;
break;
case ML_NOOP3:
r = callback(ml, offset, ML_NO_ARGS, op);
sizeof = 3;
break;
case ML_NOOP4:
r = callback(ml, offset, ML_NO_ARGS, op);
sizeof = 4;
break;
case ML_NOOP:
case ML_START:
case ML_STOP:
r = callback(ml, offset, ML_NO_ARGS, op);
sizeof = 1;
break;
default:
TRACE('(ml_walkVars) unknown op: ' + ml[offset], ' at', offset);
ml_throw(ml, offset, '(ml_walkVars) unknown op');
}
ASSERT(sizeof > 0, 'ops should occupy space');
if (r !== undefined) return r;
offset += sizeof;
}
}
function ml_validateSkeleton(ml, msg) {
TRACE_SILENT('--- ml_validateSkeleton', msg);
let started = false;
let stopped = false;
ml_walk(ml, 0, (ml, offset, op) => {
if (op === ML_START && offset === 0) started = true;
if (op === ML_START && offset !== 0) ml_throw(ml, offset, 'ml_validateSkeleton: Found ML_START at offset', offset);
if (op === ML_STOP) stopped = true;
else if (stopped) ml_throw(ml, offset, 'ml_validateSkeleton: Should stop after encountering a stop but did not');
});
if (!started || !stopped) ml_throw(ml, ml.length, 'ml_validateSkeleton: Missing a ML_START or ML_STOP');
TRACE_SILENT('--- PASS ml_validateSkeleton');
return true;
}
function ml_getRecycleOffset(ml, fromOffset, requiredSize) {
TRACE(' - ml_getRecycleOffset looking for at least', requiredSize, 'bytes of free space');
ASSERT(typeof fromOffset === 'number' && fromOffset >= 0, 'expecting fromOffset', fromOffset);
ASSERT(typeof requiredSize === 'number' && requiredSize > 0, 'expecting size', requiredSize);
// find a jump which covers at least the requiredSize
return ml_walk(ml, fromOffset, (ml, offset, op) => {
TRACE(' - considering op', op, 'at', offset);
if (op === ML_JMP || op === ML_JMP32) {
let size = ml_getOpSizeSlow(ml, offset);
TRACE(' - found jump of', size, 'bytes at', offset + ', wanted', requiredSize, (requiredSize <= size ? ' so is ok!' : ' so is too small'));
if (size >= requiredSize) return offset;
}
});
}
function ml_getRecycleOffsets(ml, fromOffset, slotCount, sizePerSlot) {
TRACE(' - ml_getRecycleOffsets looking for empty spaces to fill', slotCount, 'times', sizePerSlot, 'bytes');
ASSERT(typeof fromOffset === 'number' && fromOffset >= 0, 'expecting fromOffset', fromOffset);
ASSERT(typeof slotCount === 'number' && slotCount > 0, 'expecting slotCount', slotCount);
ASSERT(typeof sizePerSlot === 'number' && sizePerSlot > 0, 'expecting sizePerSlot', sizePerSlot);
let spaces = [];
// find a jump which covers at least the requiredSize
ml_walk(ml, fromOffset, (ml, offset, op) => {
TRACE(' - considering op', op, 'at', offset);
if (op === ML_JMP || op === ML_JMP32) {
let size = ml_getOpSizeSlow(ml, offset);
TRACE(' - found jump of', size, 'bytes at', offset + ', wanted', sizePerSlot, (sizePerSlot <= size ? ' so is ok!' : ' so is too small'));
if (size >= sizePerSlot) {
spaces.push(offset); // only add it once!
do { // remove as many from count as there fit in this empty space
--slotCount;
size -= sizePerSlot;
} while (slotCount && size >= sizePerSlot);
if (!slotCount) return true;
}
}
});
if (slotCount) return false; // unable to collect enough spaces
return spaces;
}
function ml_recycles(ml, bins, loops, sizeofOp, callback) {
let i = 0;
while (i < loops) {
let currentRecycleOffset = bins.pop();
ASSERT(ml_dec8(ml, currentRecycleOffset) === ML_JMP, 'should only get jumps here'); // might trap a case where we clobber
let sizeLeft = ml_getOpSizeSlow(ml, currentRecycleOffset);
ASSERT(sizeLeft >= sizeofOp, 'this is what should have been asked for when getting recycled spaces');
do {
let stop = callback(currentRecycleOffset, i, sizeLeft);
if (stop) return;
++i;
sizeLeft -= sizeofOp;
currentRecycleOffset += sizeofOp;
} while (sizeLeft >= sizeofOp && i < loops);
if (sizeLeft) ml_compileJumpSafe(ml, currentRecycleOffset, sizeLeft);
ASSERT(ml_validateSkeleton(ml), 'ml_recycles'); // cant check earlier
}
}
function ml_getOpSizeSlow(ml, offset) {
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && offset >= 0 && offset < ml.length, 'ml_getOpSizeSlow OOB');
// this is much slower compared to using the constants because it has to read from the ML
// this function exists to suplement recycling, where you must read the size of the jump
// otherwise you won't know how much space is left after recycling
let size = ml_sizeof(ml, offset, ml[offset]);
TRACE_SILENT(' - ml_getOpSizeSlow', offset, ml.length, '-->', size);
return size;
}
function ml_recycleC3(ml, offset, op, indexA, indexB, indexC) {
// explicitly rewrite a count with len=3
let jumpOp = ml_dec8(ml, offset);
TRACE('- ml_recycleC3 | offset=', offset, ', op=', op, indexA, indexB, indexC, jumpOp);
ASSERT(jumpOp === ML_JMP || jumpOp === ML_JMP32, 'expecting to recycle a space that starts with a jump');
ASSERT((jumpOp === ML_JMP ? SIZEOF_V + ml_dec16(ml, offset + 1) : SIZEOF_W + ml_dec32(ml, offset + 1)) >= SIZEOF_C + 6, 'a c3 should fit'); // op + len + 3*2
let currentSize = (jumpOp === ML_JMP ? SIZEOF_V + ml_dec16(ml, offset + 1) : SIZEOF_W + ml_dec32(ml, offset + 1));
let newSize = SIZEOF_C + 6;
let remainsEmpty = currentSize - newSize;
if (remainsEmpty < 0) THROW('recycled OOB');
TRACE('- putting a c3', op, 'at', offset, ', old size=', currentSize, ', new size=', newSize, ', leaving', remainsEmpty, 'for a jump');
ml_enc8(ml, offset, op);
ml_enc16(ml, offset + 1, 3);
ml_enc16(ml, offset + 3, indexA);
ml_enc16(ml, offset + 5, indexB);
ml_enc16(ml, offset + 7, indexC);
if (remainsEmpty) ml_compileJumpSafe(ml, offset + newSize, remainsEmpty);
}
function ml_recycleVV(ml, offset, op, indexA, indexB) {
let jumpOp = ml_dec8(ml, offset);
TRACE('- ml_recycleVV', offset, op, indexA, indexB, jumpOp);
ASSERT(jumpOp === ML_JMP || jumpOp === ML_JMP32, 'expecting to recycle a space that starts with a jump');
ASSERT((jumpOp === ML_JMP ? SIZEOF_V + ml_dec16(ml, offset + 1) : SIZEOF_W + ml_dec32(ml, offset + 1)) >= SIZEOF_VV, 'a vv should fit'); // op + len + 3*2
let currentSize = (jumpOp === ML_JMP ? SIZEOF_V + ml_dec16(ml, offset + 1) : SIZEOF_W + ml_dec32(ml, offset + 1));
let remainsEmpty = currentSize - SIZEOF_VV;
if (remainsEmpty < 0) THROW('recycled OOB');
TRACE('- putting a vv', op, 'at', offset, 'of size', currentSize, 'leaving', remainsEmpty, 'for a jump');
ml_enc8(ml, offset, op);
ml_enc16(ml, offset + 1, indexA);
ml_enc16(ml, offset + 3, indexB);
if (remainsEmpty) ml_compileJumpSafe(ml, offset + SIZEOF_VV, remainsEmpty);
}
function ml__debug(ml, offset, max, problem, mlAlways, _from_ml_throw) {
let getDomain = problem && problem.getDomain;
let names = problem && problem.varNames;
function ml_index(offset) {
let index = _dec16(ml, offset);
return '{index=' + index + (problem && index < names.length ? ',name=' + names[index] : '') + (problem ? ',' + domain__debug(getDomain(index)) : '') + '}';
}
function ml_16(offset) {
return _dec16(ml, offset);
}
let AB; // grrr switches and let are annoying
let rv = [];
if (max < 0) max = ml.length;
let pc = offset;
let count = 0;
while (count++ < max && pc < ml.length) {
let name = '';
let op = ml[pc];
/* eslint-disable no-fallthrough */// should have an option to allow it when explicitly stated like below...
switch (op) {
case ML_START:
if (pc !== 0) {
TRACE('collected debugs up to error:', rv);
if (!_from_ml_throw) ml_throw(ml, pc, 'ML_START at non-zero');
rv.push('unused_error(0)');
return rv.join('\n');
}
break;
case ML_IMP:
if (!name) name = '->';
/* fall-through */
case ML_NIMP:
if (!name) name = '!->';
/* fall-through */
case ML_LT:
if (!name) name = '<';
/* fall-through */
case ML_LTE:
if (!name) name = '<=';
/* fall-through */
case ML_XOR:
if (!name) name = '^';
rv.push(ml_index(pc + OFFSET_C_A) + ' ' + name + ' ' + ml_index(pc + OFFSET_C_B));
break;
case ML_ISLT:
if (!name) name = '<?';
/* fall-through */
case ML_ISLTE:
if (!name) name = '<=?';
AB = ml_index(pc + 1) + ' ' + name + ' ' + ml_index(pc + 3);
rv.push(ml_index(pc + 5) + ' = ' + AB);
break;
case ML_SUM:
if (!name) name = 'sum';
/* fall-through */
case ML_PRODUCT:
if (!name) name = 'product';
/* fall-through */
case ML_ISALL:
if (!name) name = 'isall';
/* fall-through */
case ML_ISDIFF:
if (!name) name = 'isdiff';
/* fall-through */
case ML_ISNALL:
if (!name) name = 'isnall';
/* fall-through */
case ML_ISSAME:
if (!name) name = 'issame';
/* fall-through */
case ML_ISSOME:
if (!name) name = 'issome';
/* fall-through */
case ML_ISNONE:
if (!name) name = 'isnone';
let vars = '';
let varcount = ml_16(pc + 1);
for (let i = 0; i < varcount; ++i) {
vars += ml_index(pc + SIZEOF_C + i * 2) + ' ';
}
vars = name + '(' + vars + ')';
vars = ml_index(pc + SIZEOF_C + varcount * 2) + ' = ' + vars;
rv.push(vars);
break;
case ML_ALL:
if (!name) name = 'all';
/* fall-through */
case ML_NALL:
if (!name) name = 'nall';
/* fall-through */
case ML_SAME:
if (!name) name = 'same';
/* fall-through */
case ML_SOME:
if (!name) name = 'some';
/* fall-through */
case ML_NONE:
if (!name) name = 'none';
/* fall-through */
case ML_XNOR:
if (!name) name = 'xnor';
/* fall-through */
case ML_DIFF:
if (!name) name = 'diff';
let xvars = '';
let xvarcount = ml_16(pc + 1);
for (let i = 0; i < xvarcount; ++i) {
xvars += ml_index(pc + SIZEOF_C + i * 2) + ' ';
}
xvars = name + '(' + xvars + ')';
rv.push(xvars);
break;
case ML_MINUS:
if (!name) name = '-';
/* fall-through */
case ML_DIV:
if (!name) name = '/';
AB = ml_index(pc + 1) + ' ' + name + ' ' + ml_index(pc + 3);
rv.push(ml_index(pc + 5) + ' = ' + AB);
break;
case ML_JMP:
rv.push('jmp(' + _dec16(ml, pc + 1) + ')');
break;
case ML_JMP32:
rv.push('jmp32(' + _dec32(ml, pc + 1) + ')');
break;
case ML_NOBOOL:
rv.push('nobool(' + _dec16(ml, pc + 1) + ')');
break;
case ML_NOLEAF:
rv.push('noleaf(' + _dec16(ml, pc + 1) + ')');
break;
case ML_NOOP:
rv.push('noop(1)');
break;
case ML_NOOP2:
rv.push('noop(2)');
break;
case ML_NOOP3:
rv.push('noop(3)');
break;
case ML_NOOP4:
rv.push('noop(4)');
break;
case ML_STOP:
rv.push('stop()');
break;
default:
THROW('add me [pc=' + pc + ', op=' + ml[pc] + ']');
}
let size = ml_sizeof(ml, pc, op);
//getTerm().log('size was:', size, 'rv=', rv);
if (max !== 1 || mlAlways) rv.push('\x1b[90m' + size + 'b (' + pc + ' ~ ' + (pc + size) + ') -> 0x ' + [...ml.slice(pc, pc + Math.min(size, 100))].map(c => (c < 16 ? '0' : '') + c.toString(16)).join(' ') + (size > 100 ? '... (trunced)' : '') + '\x1b[0m');
pc += size;
}
return max === 1 ? rv.join('\n') : ' ## ML Debug:\n' + rv.join('\n') + '\n ## End of ML Debug' + ((offset || pc < ml.length) ? offset ? ' (did not start at begin of ml!)' : ' (did not list all ops, ml at ' + pc + ' / ' + ml.length + '))...' : '') + '\n';
}
function ml__opName(op) {
ASSERT(typeof op === 'number', 'op should be a constant number');
switch (op) {
case ML_ALL: return 'ML_ALL';
case ML_START: return 'ML_START';
case ML_SAME: return 'ML_SAME';
case ML_LT: return 'ML_LT';
case ML_LTE: return 'ML_LTE';
case ML_XOR: return 'ML_XOR';
case ML_XNOR: return 'ML_XNOR';
case ML_IMP: return 'ML_IMP';
case ML_NIMP: return 'ML_NIMP';
case ML_ISSAME: return 'ML_ISSAME';
case ML_ISDIFF: return 'ML_ISDIFF';
case ML_ISLT: return 'ML_ISLT';
case ML_ISLTE: return 'ML_ISLTE';
case ML_SUM: return 'ML_SUM';
case ML_PRODUCT: return 'ML_PRODUCT';
case ML_ISALL: return 'ML_ISALL';
case ML_ISNALL: return 'ML_ISNALL';
case ML_ISSOME: return 'ML_ISSOME';
case ML_ISNONE: return 'ML_ISNONE';
case ML_NALL: return 'ML_NALL';
case ML_SOME: return 'ML_SOME';
case ML_NONE: return 'ML_NONE';
case ML_DIFF: return 'ML_DISTINCT';
case ML_MINUS: return 'ML_MINUS';
case ML_DIV: return 'ML_DIV';
case ML_NOBOOL: return 'ML_NOBOOL';
case ML_NOLEAF: return 'ML_NOLEAF';
case ML_JMP: return 'ML_JMP';
case ML_JMP32: return 'ML_JMP32';
case ML_NOOP: return 'ML_NOOP';
case ML_NOOP2: return 'ML_NOOP2';
case ML_NOOP3: return 'ML_NOOP3';
case ML_NOOP4: return 'ML_NOOP4';
case ML_STOP: return 'ML_STOP';
default:
THROW('[ML] unknown op, fixme [' + op + ']');
}
}
function ml_throw(ml, offset, msg) {
let term = getTerm();
term.error('\nThere was an ML related error;', msg);
let before = ml.slice(Math.max(0, offset - 30), offset);
let after = ml.slice(offset, offset + 20);
term.error('ML at error (offset=' + offset + '/' + ml.length + '):', before, after);
term.error('->', ml__debug(ml, offset, 1, undefined, true, true));
THROW(msg);
}
function ml_getOpList(ml) {
let pc = 0;
let rv = [];
while (pc < ml.length) {
let op = ml[pc];
switch (op) {
case ML_START:
if (pc !== 0) {
rv.push('error(0)');
return rv.join(',');
}
break;
case ML_SAME:
rv.push('same');
break;
case ML_LT:
rv.push('lt');
break;
case ML_LTE:
rv.push('lte');
break;
case ML_ALL:
rv.push('all');
break;
case ML_NONE:
rv.push('none');
break;
case ML_XOR:
rv.push('xor');
break;
case ML_XNOR:
rv.push('xnor');
break;
case ML_IMP:
rv.push('imp');
break;
case ML_NIMP:
rv.push('nimp');
break;
case ML_ISLT:
rv.push('islt');
break;
case ML_ISLTE:
rv.push('islte');
break;
case ML_SUM:
rv.push('sum');
break;
case ML_PRODUCT:
rv.push('product');
break;
case ML_ISALL:
rv.push('isall');
break;
case ML_ISDIFF:
rv.push('isdiff');
break;
case ML_ISNALL:
rv.push('isnall');
break;
case ML_ISNONE:
rv.push('isnone');
break;
case ML_ISSAME:
rv.push('issame');
break;
case ML_ISSOME:
rv.push('issome');
break;
case ML_NALL:
rv.push('nall');
break;
case ML_SOME:
rv.push('some');
break;
case ML_DIFF:
rv.push('diff');
break;
case ML_MINUS:
rv.push('minus');
break;
case ML_DIV:
rv.push('div');
break;
case ML_NOBOOL:
case ML_NOLEAF:
case ML_JMP:
case ML_JMP32:
case ML_NOOP:
case ML_NOOP2:
case ML_NOOP3:
case ML_NOOP4:
case ML_STOP:
break;
default:
rv.push('??!??');
}
pc += ml_sizeof(ml, pc, op);
}
return rv.sort((a, b) => a < b ? -1 : 1).join(',');
}
function ml_heapSort16bitInline(ml, offset, argCount) {
_ml_heapSort16bitInline(ml, offset, argCount);
//TRACE(' - op now:', ml__debug(ml, offset-SIZEOF_C, 1))
TRACE(' ### </ml_heapSort16bitInline> values after:', new Array(argCount).fill(0).map((_, i) => _dec16(ml, offset + i * 2)).join(' '), 'buf:', ml.slice(offset, offset + argCount * 2).join(' '));
ASSERT(ml_validateSkeleton(ml, 'ml_heapSort16bitInline'));
}
function _ml_heapSort16bitInline(ml, offset, argCount) {
ASSERT(ml instanceof Uint8Array, 'ml is Uint8Array');
ASSERT(typeof offset === 'number' && (offset === 0 || (offset > 0 && offset < ml.length)), 'valid offset', ml.length, offset, argCount);
ASSERT(typeof argCount === 'number' && (argCount === 0 || (argCount > 0 && offset + argCount * 2 <= ml.length)), 'valid count', ml.length, offset, argCount);
TRACE(' ### <ml_heapSort16bitInline>, argCount=', argCount, ', offset=', offset, ', buf=', ml.slice(offset, offset + argCount * 2));
TRACE(' - values before:', new Array(argCount).fill(0).map((_, i) => _dec16(ml, offset + i * 2)).join(' '));
if (argCount <= 1) {
TRACE(' - (argCount <= 1 so finished)');
return;
}
ml_heapify(ml, offset, argCount);
let end = argCount - 1;
while (end > 0) {
TRACE(' - swapping first elemement (should be biggest of values left to do) [', _dec16(ml, offset), '] with last [', _dec16(ml, offset + end * 2), '] and reducing end [', end, '->', end - 1, ']');
ml_swap16(ml, offset, offset + end * 2);
TRACE(' - (total) buffer now: Uint8Array(', [].map.call(ml.slice(offset, offset + argCount * 2), b => (b < 16 ? '0' : '') + b.toString(16)).join(' '), ')');
--end;
ml_heapRepair(ml, offset, 0, end);
}
}
function ml_heapParent(index) {
return Math.floor((index - 1) / 2);
}
function ml_heapLeft(index) {
return index * 2 + 1;
}
function ml_heapRight(index) {
return index * 2 + 2;
}
function ml_heapify(ml, offset, len) {
TRACE(' - ml_heapify', ml.slice(offset, offset + len * 2), offset, len);
let start = ml_heapParent(len - 1);
while (start >= 0) {
ml_heapRepair(ml, offset, start, len - 1);
--start; // wont this cause it to do it redundantly twice?
}
TRACE(' - ml_heapify end');
}
function ml_heapRepair(ml, offset, startIndex, endIndex) {
TRACE(' - ml_heapRepair', offset, startIndex, endIndex, 'Uint8Array(', [].map.call(ml.slice(offset + startIndex * 2, offset + startIndex * 2 + (endIndex - startIndex + 1) * 2), b => (b < 16 ? '0' : '') + b.toString(16)).join(' '), ')');
let parentIndex = startIndex;
let parentValue = ml_dec16(ml, offset + parentIndex * 2);
let leftIndex = ml_he