clvm_tools
Version:
Javascript implementation of clvm_tools
291 lines (289 loc) • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.make_do_opt = exports.optimize_sexp = exports.apply_null_optimizer = exports.APPLY_NULL_PATTERN_1 = exports.quote_null_optimizer = exports.QUOTE_PATTERN_1 = exports.path_optimizer = exports.REST_ATOM_PATTERN = exports.FIRST_ATOM_PATTERN = exports.cons_optimizer = exports.CONS_OPTIMIZER_PATTERN_REST = exports.CONS_OPTIMIZER_PATTERN_FIRST = exports.children_optimizer = exports.var_change_optimizer_cons_eval = exports.VAR_CHANGE_OPTIMIZER_CONS_EVAL_PATTERN = exports.sub_args = exports.path_from_args = exports.cons_r = exports.cons_f = exports.CONS_PATTERN = exports.cons_q_a_optimizer = exports.CONS_Q_A_OPTIMIZER_PATTERN = exports.is_args_call = exports.constant_optimizer = exports.seems_constant = exports.non_nil = exports.RAISE_ATOM = exports.CONS_ATOM = exports.REST_ATOM = exports.FIRST_ATOM = exports.APPLY_ATOM = exports.QUOTE_ATOM = void 0;
const clvm_1 = require("clvm");
const pattern_match_1 = require("../../clvm_tools/pattern_match");
const binutils_1 = require("../../clvm_tools/binutils");
const NodePath_1 = require("../../clvm_tools/NodePath");
const helpers_1 = require("./helpers");
const print_1 = require("../../platform/print");
exports.QUOTE_ATOM = clvm_1.KEYWORD_TO_ATOM["q"];
exports.APPLY_ATOM = clvm_1.KEYWORD_TO_ATOM["a"];
exports.FIRST_ATOM = clvm_1.KEYWORD_TO_ATOM["f"];
exports.REST_ATOM = clvm_1.KEYWORD_TO_ATOM["r"];
exports.CONS_ATOM = clvm_1.KEYWORD_TO_ATOM["c"];
exports.RAISE_ATOM = clvm_1.KEYWORD_TO_ATOM["x"];
const DEBUG_OPTIMIZATIONS = 0;
function non_nil(sexp) {
return sexp.listp() || (sexp.atom && sexp.atom.length > 0);
}
exports.non_nil = non_nil;
function seems_constant(sexp) {
if (!sexp.listp()) {
// note that `0` is a constant
return !non_nil(sexp);
}
const operator = sexp.first();
if (!operator.listp()) {
const atom = operator.atom && operator.atom.hex();
if (atom === exports.QUOTE_ATOM) {
return true;
}
else if (atom === exports.RAISE_ATOM) {
return false;
}
}
else if (!seems_constant(operator)) {
return false;
}
for (const _ of sexp.rest().as_iter()) {
if (!seems_constant(_)) {
return false;
}
}
return true;
}
exports.seems_constant = seems_constant;
function constant_optimizer(r, eval_f) {
/*
If the expression does not depend upon @ anywhere,
it's a constant. So we can simply evaluate it and
return the quoted result.
*/
if (seems_constant(r) && non_nil(r)) {
const [, r1] = eval_f(r, clvm_1.SExp.null());
r = clvm_1.SExp.to(helpers_1.quote(r1));
}
return r;
}
exports.constant_optimizer = constant_optimizer;
function is_args_call(r) {
return !r.listp() && r.as_int() === 1;
}
exports.is_args_call = is_args_call;
exports.CONS_Q_A_OPTIMIZER_PATTERN = binutils_1.assemble("(a (q . (: . sexp)) (: . args))");
function cons_q_a_optimizer(r, eval_f) {
/*
This applies the transform
(a (q . SEXP) @) => SEXP
*/
const t1 = pattern_match_1.match(exports.CONS_Q_A_OPTIMIZER_PATTERN, r);
if (t1 && is_args_call(t1["args"])) {
return t1["sexp"];
}
return r;
}
exports.cons_q_a_optimizer = cons_q_a_optimizer;
exports.CONS_PATTERN = binutils_1.assemble("(c (: . first) (: . rest)))");
function cons_f(args) {
const t = pattern_match_1.match(exports.CONS_PATTERN, args);
if (t) {
return t["first"];
}
return clvm_1.SExp.to([clvm_1.h(exports.FIRST_ATOM), args]);
}
exports.cons_f = cons_f;
function cons_r(args) {
const t = pattern_match_1.match(exports.CONS_PATTERN, args);
if (t) {
return t["rest"];
}
return clvm_1.SExp.to([clvm_1.h(exports.REST_ATOM), args]);
}
exports.cons_r = cons_r;
function path_from_args(sexp, new_args) {
const v = sexp.as_int();
if (v <= 1) {
return new_args;
}
sexp = clvm_1.SExp.to(v >> 1);
if (v & 1) {
return path_from_args(sexp, cons_r(new_args));
}
return path_from_args(sexp, cons_f(new_args));
}
exports.path_from_args = path_from_args;
function sub_args(sexp, new_args) {
if (sexp.nullp() || !sexp.listp()) {
return path_from_args(sexp, new_args);
}
let first = sexp.first();
if (first.listp()) {
first = sub_args(first, new_args);
}
else {
const op = first.atom;
if (op.hex() === exports.QUOTE_ATOM) {
return sexp;
}
}
const args = [first];
for (const _ of sexp.rest().as_iter()) {
args.push(sub_args(_, new_args));
}
return clvm_1.SExp.to(args);
}
exports.sub_args = sub_args;
exports.VAR_CHANGE_OPTIMIZER_CONS_EVAL_PATTERN = binutils_1.assemble("(a (q . (: . sexp)) (: . args))");
function var_change_optimizer_cons_eval(r, eval_f) {
/*
This applies the transform
(a (q . (op SEXP1...)) (ARGS)) => (q . RET_VAL) where ARGS != @
via
(op (a SEXP1 (ARGS)) ...) (ARGS)) and then "children_optimizer" of this.
In some cases, this can result in a constant in some of the children.
If we end up needing to push the "change of variables" to only one child, keep
the optimization. Otherwise discard it.
*/
const t1 = pattern_match_1.match(exports.VAR_CHANGE_OPTIMIZER_CONS_EVAL_PATTERN, r);
if (t1 === clvm_1.None) {
return r;
}
const original_args = t1["args"];
const original_call = t1["sexp"];
const new_eval_sexp_args = sub_args(original_call, original_args);
// Do not iterate into a quoted value as if it were a list
if (seems_constant(new_eval_sexp_args)) {
const opt_operands = optimize_sexp(new_eval_sexp_args, eval_f);
return clvm_1.SExp.to(opt_operands);
}
const new_operands = [];
for (const item of new_eval_sexp_args.as_iter()) {
new_operands.push(item);
}
const opt_operands = new_operands.map(_ => optimize_sexp(_, eval_f));
const non_constant_count = opt_operands.reduce((acc, val) => {
return acc + (val.listp() && !val.first().equal_to(clvm_1.h(exports.QUOTE_ATOM)) ? 1 : 0);
}, 0);
if (non_constant_count < 1) {
return clvm_1.SExp.to(opt_operands);
}
return r;
}
exports.var_change_optimizer_cons_eval = var_change_optimizer_cons_eval;
function children_optimizer(r, eval_f) {
// Recursively apply optimizations to all non-quoted child nodes.
if (!r.listp()) {
return r;
}
const operator = r.first();
if (!operator.listp()) {
const op = operator.atom;
if (op.hex() === exports.QUOTE_ATOM) {
return r;
}
}
const optimized = [];
for (const _ of r.as_iter()) {
optimized.push(optimize_sexp(_, eval_f));
}
return clvm_1.SExp.to(optimized);
}
exports.children_optimizer = children_optimizer;
exports.CONS_OPTIMIZER_PATTERN_FIRST = binutils_1.assemble("(f (c (: . first) (: . rest)))");
exports.CONS_OPTIMIZER_PATTERN_REST = binutils_1.assemble("(r (c (: . first) (: . rest)))");
function cons_optimizer(r, eval_f) {
/*
This applies the transform
(f (c A B)) => A
and
(r (c A B)) => B
*/
let t1 = pattern_match_1.match(exports.CONS_OPTIMIZER_PATTERN_FIRST, r);
if (t1) {
return t1["first"];
}
t1 = pattern_match_1.match(exports.CONS_OPTIMIZER_PATTERN_REST, r);
if (t1) {
return t1["rest"];
}
return r;
}
exports.cons_optimizer = cons_optimizer;
exports.FIRST_ATOM_PATTERN = binutils_1.assemble("(f ($ . atom))");
exports.REST_ATOM_PATTERN = binutils_1.assemble("(r ($ . atom))");
function path_optimizer(r, eval_f) {
/*
This applies the transform
(f N) => A
and
(r N) => B
*/
let t1 = pattern_match_1.match(exports.FIRST_ATOM_PATTERN, r);
if (t1 && non_nil(t1["atom"])) {
let node = new NodePath_1.NodePath(t1["atom"].as_bigint());
node = node.add(NodePath_1.LEFT);
return clvm_1.SExp.to(node.as_short_path());
}
t1 = pattern_match_1.match(exports.REST_ATOM_PATTERN, r);
if (t1 && non_nil(t1["atom"])) {
let node = new NodePath_1.NodePath(t1["atom"].as_bigint());
node = node.add(NodePath_1.RIGHT);
return clvm_1.SExp.to(node.as_short_path());
}
return r;
}
exports.path_optimizer = path_optimizer;
exports.QUOTE_PATTERN_1 = binutils_1.assemble("(q . 0)");
function quote_null_optimizer(r, eval_f) {
// This applies the transform `(q . 0)` => `0`
const t1 = pattern_match_1.match(exports.QUOTE_PATTERN_1, r);
if (t1) {
return clvm_1.SExp.to(0);
}
return r;
}
exports.quote_null_optimizer = quote_null_optimizer;
exports.APPLY_NULL_PATTERN_1 = binutils_1.assemble("(a 0 . (: . rest))");
function apply_null_optimizer(r, eval_f) {
// This applies the transform `(a 0 ARGS)` => `0`
const t1 = pattern_match_1.match(exports.APPLY_NULL_PATTERN_1, r);
if (t1) {
return clvm_1.SExp.to(0);
}
return r;
}
exports.apply_null_optimizer = apply_null_optimizer;
function optimize_sexp(r, eval_f) {
/*
Optimize an s-expression R written for clvm to R_opt where
(a R args) == (a R_opt args) for ANY args.
*/
if (r.nullp() || !r.listp()) {
return r;
}
const OPTIMIZERS = [
cons_optimizer,
constant_optimizer,
cons_q_a_optimizer,
var_change_optimizer_cons_eval,
children_optimizer,
path_optimizer,
quote_null_optimizer,
apply_null_optimizer,
];
while (r.listp()) {
const start_r = r;
let opt = OPTIMIZERS[0];
for (opt of OPTIMIZERS) {
r = opt(r, eval_f);
if (!start_r.equal_to(r)) {
break;
}
}
if (start_r.equal_to(r)) {
return r;
}
if (DEBUG_OPTIMIZATIONS) {
print_1.print(`OPT-${opt.name}[${start_r}] => ${r}`);
}
}
return r;
}
exports.optimize_sexp = optimize_sexp;
function make_do_opt(run_program) {
return function do_opt(args) {
return clvm_1.t(1, optimize_sexp(args.first(), run_program));
};
}
exports.make_do_opt = make_do_opt;