Skip to content

Commit

Permalink
Merge pull request #28 from Chia-Mine/v1.0.6
Browse files Browse the repository at this point in the history
V1.0.6
  • Loading branch information
ChiaMineJP authored Aug 17, 2021
2 parents 1223fde + f1eedad commit c15fdd4
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 89 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## [1.0.6]
This version is compatible with [`ab4560900cf475ff515054bec0ca9a4491aca366`](https://github.com/Chia-Network/clvm/tree/ab4560900cf475ff515054bec0ca9a4491aca366) of [[email protected]](https://github.com/Chia-Network/clvm)

### Changed
- Improved `bigint_from_bytes` around 3-10 times faster.
- Improved `int_from_bytes` performance.
- Improved `bigint_to_bytes` around 2 times faster.
- Improved `int_to_bytes` performance.
### Fixed
- Fixed an issue where utility function `division`/`modulo` returned wrong values in some cases.
### Added
- Added `divmod` utility function.

## [1.0.5]
This version is compatible with [`ab4560900cf475ff515054bec0ca9a4491aca366`](https://github.com/Chia-Network/clvm/tree/ab4560900cf475ff515054bec0ca9a4491aca366) of [[email protected]](https://github.com/Chia-Network/clvm)

Expand Down Expand Up @@ -200,6 +213,7 @@ At this version, I've managed to improve test complete time to `79s` -> `2s` by
Initial (beta) release.

<!--[Unreleased]: https://github.com/Chia-Mine/clvm-js/compare/v0.0.1...v0.0.2-->
[1.0.6]: https://github.com/Chia-Mine/clvm-js/compare/v1.0.5...v1.0.6
[1.0.5]: https://github.com/Chia-Mine/clvm-js/compare/v1.0.4...v1.0.5
[1.0.4]: https://github.com/Chia-Mine/clvm-js/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/Chia-Mine/clvm-js/compare/v1.0.2...v1.0.3
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clvm",
"version": "1.0.5",
"version": "1.0.6",
"author": "Admin ChiaMineJP <[email protected]>",
"description": "Javascript implementation of chia lisp",
"license": "MIT",
Expand Down
22 changes: 20 additions & 2 deletions src/__type_compatibility__.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,21 +450,39 @@ export class Stream {
* In javascript, `-8 / 5 === -1` while `-8 / 5 == -2` in Python
*/
export function division(a: bigint, b: bigint): bigint {
if(a === BigInt(0)){
return a;
}
else if(b === BigInt(0)){
throw new Error("Division by zero!");
}
else if(a > BigInt(0) && b > BigInt(0) && a < b){
return BigInt(0);
}
else if(a < BigInt(0) && b < BigInt(0) && a > b){
return BigInt(0);
}

const div = a / b;
if(a === div*b){
return div;
}
if(div >= BigInt(0)){
else if(div > BigInt(0)){
return div;
}
return div - BigInt(1);
}

/**
* Python's style modulo.
* In javascript, `-8 % 5 === -3` while `-8 / 5 == 2` in Python
* In javascript, `-8 % 5 === -3` while `-8 % 5 == 2` in Python
*/
export function modulo(a: bigint, b: bigint): bigint {
const div = division(a, b);
return a - b*div;
}

export function divmod(a: bigint, b: bigint): Tuple<bigint, bigint> {
const div = division(a, b);
return t(div, a - b*div);
}
119 changes: 98 additions & 21 deletions src/casts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,32 @@ export function int_from_bytes(b: Bytes|None, option?: Partial<TConvertOption>):
}
const signed = (option && typeof option.signed === "boolean") ? option.signed : false;
let unsigned32 = 0;
for(let i=b.length-1;i>=0;i--){
const byte = b.at(i);
unsigned32 += byte * (256**((b.length-1)-i));
const ui8array = b.raw();
const dv = new DataView(ui8array.buffer, ui8array.byteOffset, ui8array.byteLength);
const bytes4Remain = dv.byteLength % 4;
const bytes4Length = (dv.byteLength - bytes4Remain) / 4;

let order = 1;
for(let i=bytes4Length-1;i>=0;i--){
const byte32 = dv.getUint32(i*4 + bytes4Remain);
unsigned32 += byte32 * order;
order = Number(BigInt(order) << BigInt(32));
}

if(bytes4Remain > 0){
if(bytes4Length === 0){
order = 1;
}
for(let i=bytes4Remain-1;i>=0;i--){
const byte = ui8array[i];
unsigned32 += byte * order;
order = Number(BigInt(order) << BigInt(8));
}
}

// If the first bit is 1, it is recognized as a negative number.
if(signed && (b.at(0) & 0x80)){
return unsigned32 - (256**b.length);
if(signed && (ui8array[0] & 0x80)){
return unsigned32 - Number(BigInt(1) << BigInt(b.length*8));
}
return unsigned32;
}
Expand All @@ -31,12 +50,31 @@ export function bigint_from_bytes(b: Bytes|None, option?: Partial<TConvertOption
}
const signed = (option && typeof option.signed === "boolean") ? option.signed : false;
let unsigned32 = BigInt(0);
for(let i=b.length-1;i>=0;i--){
const byte = b.at(i);
unsigned32 += BigInt(byte) * (BigInt(256)**(BigInt((b.length-1)-i)));
const ui8array = b.raw();
const dv = new DataView(ui8array.buffer, ui8array.byteOffset, ui8array.byteLength);
const bytes4Remain = dv.byteLength % 4;
const bytes4Length = (dv.byteLength - bytes4Remain) / 4;

let order = BigInt(1);
for(let i=bytes4Length-1;i>=0;i--){
const byte32 = dv.getUint32(i*4 + bytes4Remain);
unsigned32 += BigInt(byte32) * order;
order <<= BigInt(32);
}

if(bytes4Remain > 0){
if(bytes4Length === 0){
order = BigInt(1);
}
for(let i=bytes4Remain-1;i>=0;i--){
const byte = ui8array[i];
unsigned32 += BigInt(byte) * order;
order <<= BigInt(8);
}
}

// If the first bit is 1, it is recognized as a negative number.
if(signed && (b.at(0) & 0x80)){
if(signed && (ui8array[0] & 0x80)){
return unsigned32 - (BigInt(1) << BigInt(b.length*8));
}
return unsigned32;
Expand All @@ -54,22 +92,35 @@ export function int_to_bytes(v: number, option?: Partial<TConvertOption>): Bytes
if(!signed && v < 0){
throw new Error("OverflowError: can't convert negative int to unsigned");
}

let byte_count = 1;
const div = signed ? 1 : 0;
const b16 = 65536;
if(v > 0){
while(2**(8*byte_count - (signed ? 1 : 0)) - 1 < v){
let right_hand = (v + 1) * (div + 1);
while((b16 ** ((byte_count-1)/2 + 1)) < right_hand){
byte_count += 2;
}
right_hand = (v + 1) * (div + 1);
while (2 ** (8 * byte_count) < right_hand) {
byte_count++;
}
}
else if(v < 0){
while(2**(8*byte_count - 1) < -v){
let right_hand = (-v + 1) * (div + 1);
while((b16 ** ((byte_count-1)/2 + 1)) < right_hand){
byte_count += 2;
}
right_hand = -v * 2;
while (2 ** (8 * byte_count) < right_hand) {
byte_count++;
}
}

const needExtraByte = signed && v > 0 && ((v >> ((byte_count-1)*8)) & 0x80) > 0;
const u8 = new Uint8Array(byte_count+(needExtraByte ? 1 : 0));
const extraByte = signed && v > 0 && ((v >> ((byte_count-1)*8)) & 0x80) > 0 ? 1 : 0;
const u8 = new Uint8Array(byte_count + extraByte);
for(let i=0;i<byte_count;i++){
const j = needExtraByte ? i+1 : i;
const j = extraByte ? i+1 : i;
u8[j] = (v >> (byte_count-i-1)*8) & 0xff;
}

Expand All @@ -86,22 +137,48 @@ export function bigint_to_bytes(v: bigint, option?: Partial<TConvertOption>): By
throw new Error("OverflowError: can't convert negative int to unsigned");
}
let byte_count = 1;
const div = BigInt(signed ? 1 : 0);
const b32 = BigInt(4294967296);
if(v > 0){
while(BigInt(2)**(BigInt(8)*BigInt(byte_count) - (signed ? BigInt(1) : BigInt(0))) - BigInt(1) < v){
let right_hand = (v + BigInt(1)) * (div + BigInt(1));
while((b32 ** BigInt((byte_count-1)/4 + 1)) < right_hand){
byte_count += 4;
}
right_hand = (v + BigInt(1)) * (div + BigInt(1));
while (BigInt(2) ** (BigInt(8) * BigInt(byte_count)) < right_hand) {
byte_count++;
}
}
else if(v < 0){
while(BigInt(2)**(BigInt(8)*BigInt(byte_count) - BigInt(1)) < -v){
let right_hand = (-v + BigInt(1)) * (div + BigInt(1));
while((b32 ** BigInt((byte_count-1)/4 + 1)) < right_hand){
byte_count += 4;
}
right_hand = -v * BigInt(2);
while (BigInt(2) ** (BigInt(8) * BigInt(byte_count)) < right_hand) {
byte_count++;
}
}

const needExtraByte = signed && v > 0 && ((v >> (BigInt(byte_count-1)*BigInt(8))) & BigInt(0x80)) > BigInt(0);
const u8 = new Uint8Array(byte_count+(needExtraByte ? 1 : 0));
for(let i=0;i<byte_count;i++){
const j = needExtraByte ? i+1 : i;
u8[j] = Number((v >> (BigInt(byte_count-i-1))*BigInt(8)) & BigInt(0xff));
const extraByte = (signed && v > 0 && ((v >> (BigInt(byte_count-1)*BigInt(8))) & BigInt(0x80)) > BigInt(0)) ? 1 : 0;
const total_bytes = byte_count + extraByte;
const u8 = new Uint8Array(total_bytes);
const dv = new DataView(u8.buffer);
const byte4Remain = byte_count % 4;
const byte4Length = (byte_count - byte4Remain) / 4;

let bitmask = BigInt(0xffffffff);
for(let i=0;i<byte4Length;i++){
const num = Number((v >> BigInt(32)*BigInt(i)) & bitmask);
const pointer = extraByte + byte4Remain + (byte4Length-1 - i)*4;
dv.setUint32(pointer, num);
}
v >>= BigInt(32) * BigInt(byte4Length);
bitmask = BigInt(0xff);
for(let i=0;i<byte4Remain;i++){
const num = Number((v >> BigInt(8)*BigInt(i)) & bitmask);
const pointer = extraByte + byte4Remain-1-i;
dv.setUint8(pointer, num);
}

return new Bytes(u8);
Expand Down
7 changes: 3 additions & 4 deletions src/more_ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
STRLEN_BASE_COST,
STRLEN_COST_PER_BYTE
} from "./costs";
import {Bytes, list, Stream, t, division, modulo} from "./__type_compatibility__";
import {Bytes, list, Stream, t, division, modulo, divmod, Tuple} from "./__type_compatibility__";
import {EvalError} from "./EvalError";
import {bigint_from_bytes, bigint_to_bytes, limbs_for_int} from "./casts";
import {isAtom} from "./CLVMObject";
Expand Down Expand Up @@ -174,7 +174,7 @@ export function op_multiply(args: SExp){
const [r, rs] = o as [bigint, number];
cost += MUL_COST_PER_OP;
cost += (rs + vs) * MUL_LINEAR_COST_PER_BYTE;
cost += ((rs * vs) / MUL_SQUARE_COST_PER_BYTE_DIVIDER) >> 0;
cost += ((rs * vs) / MUL_SQUARE_COST_PER_BYTE_DIVIDER) | 0;
v = v * r;
vs = limbs_for_int(v);
}
Expand All @@ -190,8 +190,7 @@ export function op_divmod(args: SExp){
throw new EvalError("divmod with 0", SExp.to(i0));
}
cost += (l0+l1)*DIVMOD_COST_PER_BYTE;
const q = division(i0, i1); // i0 / i1
const r = modulo(i0, i1); // i0 % i1
const [q, r] = divmod(i0, i1) as Tuple<bigint, bigint>;
const q1 = SExp.to(q);
const r1 = SExp.to(r);
cost += ((q1.atom as Bytes).length + (r1.atom as Bytes).length) * MALLOC_COST_PER_BYTE;
Expand Down
72 changes: 36 additions & 36 deletions src/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,47 +176,47 @@ export function* args_len(op_name: string, args: SExp){
}

/*
# unknown ops are reserved if they start with 0xffff
# otherwise, unknown ops are no-ops, but they have costs. The cost is computed
# like this:
unknown ops are reserved if they start with 0xffff
otherwise, unknown ops are no-ops, but they have costs. The cost is computed
like this:
# byte index (reverse):
# | 4 | 3 | 2 | 1 | 0 |
# +---+---+---+---+------------+
# | multiplier |XX | XXXXXX |
# +---+---+---+---+---+--------+
# ^ ^ ^
# | | + 6 bits ignored when computing cost
# cost_multiplier |
# + 2 bits
# cost_function
byte index (reverse):
| 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+------------+
| multiplier |XX | XXXXXX |
+---+---+---+---+---+--------+
^ ^ ^
| | + 6 bits ignored when computing cost
cost_multiplier |
+ 2 bits
cost_function
# 1 is always added to the multiplier before using it to multiply the cost, this
# is since cost may not be 0.
1 is always added to the multiplier before using it to multiply the cost, this
is since cost may not be 0.
# cost_function is 2 bits and defines how cost is computed based on arguments:
# 0: constant, cost is 1 * (multiplier + 1)
# 1: computed like operator add, multiplied by (multiplier + 1)
# 2: computed like operator mul, multiplied by (multiplier + 1)
# 3: computed like operator concat, multiplied by (multiplier + 1)
cost_function is 2 bits and defines how cost is computed based on arguments:
0: constant, cost is 1 * (multiplier + 1)
1: computed like operator add, multiplied by (multiplier + 1)
2: computed like operator mul, multiplied by (multiplier + 1)
3: computed like operator concat, multiplied by (multiplier + 1)
# this means that unknown ops where cost_function is 1, 2, or 3, may still be
# fatal errors if the arguments passed are not atoms.
*/
this means that unknown ops where cost_function is 1, 2, or 3, may still be
fatal errors if the arguments passed are not atoms.
*/
export function default_unknown_op(op: Bytes, args: SExp): Tuple<number, CLVMObject> {
// # any opcode starting with ffff is reserved (i.e. fatal error)
// # opcodes are not allowed to be empty
// any opcode starting with ffff is reserved (i.e. fatal error)
// opcodes are not allowed to be empty
if(op.length === 0 || op.subarray(0, 2).equal_to(Bytes.from("0xffff", "hex"))){
throw new EvalError("reserved operator", SExp.to(op));
}

/*
# all other unknown opcodes are no-ops
# the cost of the no-ops is determined by the opcode number, except the
# 6 least significant bits.
all other unknown opcodes are no-ops
the cost of the no-ops is determined by the opcode number, except the
6 least significant bits.
*/
const cost_function = (op.at(op.length-1) & 0b11000000) >> 6;
// # the multiplier cannot be 0. it starts at 1
// the multiplier cannot be 0. it starts at 1

if(op.length > 5){
throw new EvalError("invalid operator", SExp.to(op));
Expand All @@ -225,10 +225,10 @@ export function default_unknown_op(op: Bytes, args: SExp): Tuple<number, CLVMObj
// The bytes here is 4bytes or smaller. So `int_from_bytes` is enough. (No bigint_from_bytes required)
const cost_multiplier = int_from_bytes(op.subarray(0, op.length-1), {signed: false}) + 1;
/*
# 0 = constant
# 1 = like op_add/op_sub
# 2 = like op_multiply
# 3 = like op_concat
0 = constant
1 = like op_add/op_sub
2 = like op_multiply
3 = like op_concat
*/
let cost;
if(cost_function === 0){
Expand All @@ -244,7 +244,7 @@ export function default_unknown_op(op: Bytes, args: SExp): Tuple<number, CLVMObj
cost += arg_size * ARITH_COST_PER_BYTE;
}
else if(cost_function === 2){
// # like op_multiply
// like op_multiply
cost = MUL_BASE_COST;
const operands = args_len("unknown op", args);
const res = operands.next();
Expand All @@ -253,13 +253,13 @@ export function default_unknown_op(op: Bytes, args: SExp): Tuple<number, CLVMObj
for(const rs of operands){
cost += MUL_COST_PER_OP;
cost += (rs + vs) * MUL_LINEAR_COST_PER_BYTE;
cost += ((rs * vs) / MUL_SQUARE_COST_PER_BYTE_DIVIDER) >> 0;
cost += ((rs * vs) / MUL_SQUARE_COST_PER_BYTE_DIVIDER) | 0;
vs += rs;
}
}
}
else if(cost_function === 3){
// # like concat
// like concat
cost = CONCAT_BASE_COST;
let length = 0;
for(const arg of args.as_iter()){
Expand Down
Loading

0 comments on commit c15fdd4

Please sign in to comment.