Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
xbw886 committed Aug 3, 2024
2 parents 51de571 + 854f3ef commit 597a0b6
Show file tree
Hide file tree
Showing 238 changed files with 2,350 additions and 1,206 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ version: 2.1
setup: true

orbs:
path-filtering: circleci/path-filtering@1.0.0
path-filtering: circleci/path-filtering@1.1.0
continuation: circleci/[email protected]

parameters:
Expand Down
3 changes: 2 additions & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Checks: "abseil-cleanup-ctad,
-readability-identifier-length,
-readability-function-cognitive-complexity,
-readability-magic-numbers,
-readability-named-parameter"
-readability-named-parameter,
-readability-convert-member-functions-to-static"

CheckOptions:
- key: bugprone-argument-comment.StrictMode
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
Expand Down Expand Up @@ -67,6 +67,6 @@ jobs:

# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
with:
sarif_file: results.sarif
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
>
> please add your unreleased change here.
## TBD
- [Feature] Add more send/recv actions profiling

## 20240716

- [SPU] 0.9.2b0 release
- [Feature] Support jax.numpy.bitwise_count
- [Bugfix] Fix jax.numpy.signbit wrong answer with very large input

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ If you think SPU is helpful for your research or development, please consider ci

## Acknowledgement

We thank the significant contributions made by [Alibaba Gemini Lab](https://alibaba-gemini-lab.github.io).
We thank the significant contributions made by [Alibaba Gemini Lab](https://alibaba-gemini-lab.github.io) and security advisories made by [VUL337@NISL@THU](https://netsec.ccert.edu.cn/vul337).
24 changes: 12 additions & 12 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,21 @@ def _yacl():
http_archive,
name = "yacl",
urls = [
"https://github.com/secretflow/yacl/archive/refs/tags/0.4.5b1.tar.gz",
"https://github.com/secretflow/yacl/archive/refs/tags/0.4.5b3_nightly_20240722.tar.gz",
],
strip_prefix = "yacl-0.4.5b1",
sha256 = "28064053b9add0db8e1e8e648421a0579f1d3e7ee8a4bbd7bd5959cb59598088",
strip_prefix = "yacl-0.4.5b3_nightly_20240722",
sha256 = "ccca599e6ded6089c5afbb87c8f5e09383195af256caacd50089f0c7443e8604",
)

def _libpsi():
maybe(
http_archive,
name = "psi",
urls = [
"https://github.com/secretflow/psi/archive/refs/tags/v0.4.0.dev240524.tar.gz",
"https://github.com/secretflow/psi/archive/refs/tags/v0.4.1.dev240722.tar.gz",
],
strip_prefix = "psi-0.4.0.dev240524",
sha256 = "c2868fa6a9d804e6bbed9922dab6dc819ec6e180e15eafe7eb1b661302508c88",
strip_prefix = "psi-0.4.1.dev240722",
sha256 = "878cd8af2c7b9850944a27adf91f21dd4937d09d38e8365baad3b5165db8b39a",
)

def _rules_proto_grpc():
Expand Down Expand Up @@ -136,8 +136,8 @@ def _bazel_skylib():
)

def _com_github_openxla_xla():
OPENXLA_COMMIT = "9b0dd58c9b625a2e958f4fc7787a1ff5c95dbb40"
OPENXLA_SHA256 = "f150c5b49e4d4497aae2c79232f1efe2baccaa72223b21dc8715be73eab74417"
OPENXLA_COMMIT = "04f2bfe797408c9efe742b89e2e4db6cf526ebb7"
OPENXLA_SHA256 = "7e1d24737815be7607eed5f02fe7f81d97ffe358dfb7b4876f97bce8f48b3b3e"

# We need openxla to handle xla/mhlo/stablehlo
maybe(
Expand Down Expand Up @@ -169,10 +169,10 @@ def _com_github_pybind11():
http_archive,
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11.BUILD",
sha256 = "bf8f242abd1abcd375d516a7067490fb71abd79519a282d22b6e4d19282185a7",
strip_prefix = "pybind11-2.12.0",
sha256 = "51631e88960a8856f9c497027f55c9f2f9115cafb08c0005439838a05ba17bfc",
strip_prefix = "pybind11-2.13.1",
urls = [
"https://github.com/pybind/pybind11/archive/refs/tags/v2.12.0.tar.gz",
"https://github.com/pybind/pybind11/archive/refs/tags/v2.13.1.tar.gz",
],
)

Expand Down Expand Up @@ -229,7 +229,7 @@ def _com_github_microsoft_seal():
maybe(
http_archive,
name = "com_github_microsoft_seal",
sha256 = "78ef7334114de930daf7659e8ba60c5abfff85c86ec2b827a2b7c67c3c42da43",
sha256 = "acc2a1a127a85d1e1ffcca3ffd148f736e665df6d6b072df0e42fff64795a13c",
strip_prefix = "SEAL-4.1.2",
type = "tar.gz",
patch_args = ["-p1"],
Expand Down
2 changes: 1 addition & 1 deletion docs/development/add_protocols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ When kernels are implemented and registered, a new protocol is finally added.
auto* prg_state = ctx->getState<PrgState>();

// dispatch the real implementation to different fields
return DISPATCH_ALL_FIELDS(field, "aby3.mulAA", [&]() {
return DISPATCH_ALL_FIELDS(field, [&]() {
// the real protocol implementation
...
});
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/np_op_status.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions docs/reference/np_op_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,20 @@ Please check *Supported Dtypes* as well.
- uint16
- uint32

## bitwise_count

JAX NumPy Document link: https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.bitwise_count.html
### Status

**PASS**
Please check *Supported Dtypes* as well.
### Supported Dtypes

- int16
- int32
- uint16
- uint32

## bitwise_not

JAX NumPy Document link: https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.bitwise_not.html
Expand Down
6 changes: 3 additions & 3 deletions docs/reference/pphlo_doc.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PPHlo API reference
PPHLO API reference
===================

PPHlo is short for (SPU High level ops), it's the assembly language of SPU.
PPHLO is short for (Privacy-Preserving High-Level Operations), it's the assembly language of SPU.

PPHlo is built on `MLIR <https://mlir.llvm.org/>`_ infrastructure, the concrete ops definition could be found :spu_code_host:`here <spu/blob/main/libspu/dialect/pphlo/IR/ops.td>`.
PPHLO is built on `MLIR <https://mlir.llvm.org/>`_ infrastructure, the concrete ops definition could be found :spu_code_host:`here <spu/blob/main/libspu/dialect/pphlo/IR/ops.td>`.

Op List
~~~~~~~
Expand Down
58 changes: 38 additions & 20 deletions docs/reference/pphlo_op_doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ Ref https://www.tensorflow.org/xla/operation_semantics#dot.

Traits: `AlwaysSpeculatableImplTrait`

Interfaces: `ConditionallySpeculatable`, `NoMemoryEffect (MemoryEffectOpInterface)`
Interfaces: `ConditionallySpeculatable`, `InferShapedTypeOpInterface`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`

Effects: `MemoryEffects::Effect{}`

Expand Down Expand Up @@ -1626,55 +1626,63 @@ Effects: `MemoryEffects::Effect{}`
| :----: | ----------- |
| `result` | statically shaped tensor of 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values values

### `pphlo.power` (spu::pphlo::PowOp)
### `pphlo.popcnt` (spu::pphlo::PopcntOp)

_Power operator_
_Popcnt operator, ties away from zero_


Syntax:

```
operation ::= `pphlo.power` $lhs `,` $rhs attr-dict
`:` custom<SameOperandsAndResultType>(type($lhs), type($rhs), type($result))
operation ::= `pphlo.popcnt` $operand attr-dict `:` custom<SameOperandsAndResultType>(type($operand), type($result))
```

Performs element-wise exponentiation of `lhs` tensor by `rhs` tensor and produces a `result` tensor.
Performs element-wise count of the number of bits set in the `operand` tensor and produces a `result` tensor.

Ref https://github.com/openxla/stablehlo/blob/main/docs/spec.md#power
Ref https://github.com/openxla/stablehlo/blob/main/docs/spec.md#popcnt

Traits: `AlwaysSpeculatableImplTrait`, `Elementwise`, `SameOperandsAndResultShape`
Traits: `AlwaysSpeculatableImplTrait`, `Elementwise`, `SameOperandsAndResultShape`, `SameOperandsAndResultType`

Interfaces: `ConditionallySpeculatable`, `InferShapedTypeOpInterface`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`
Interfaces: `ConditionallySpeculatable`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`

Effects: `MemoryEffects::Effect{}`

#### Attributes:

<table>
<tr><th>Attribute</th><th>MLIR Type</th><th>Description</th></tr>
<tr><td><code>bits</code></td><td>::mlir::IntegerAttr</td><td>64-bit signless integer attribute</td></tr>
</table>

#### Operands:

| Operand | Description |
| :-----: | ----------- |
| `lhs` | statically shaped tensor of pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values or 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or 16-bit float or 32-bit float or 64-bit float or Secret of 16-bit float or 32-bit float or 64-bit float values or complex type with 32-bit float or 64-bit float elements or Secret of complex type with 32-bit float or 64-bit float elements values values
| `rhs` | statically shaped tensor of pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values or 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or 16-bit float or 32-bit float or 64-bit float or Secret of 16-bit float or 32-bit float or 64-bit float values or complex type with 32-bit float or 64-bit float elements or Secret of complex type with 32-bit float or 64-bit float elements values values
| `operand` | statically shaped tensor of 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values values

#### Results:

| Result | Description |
| :----: | ----------- |
| `result` | statically shaped tensor of pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values or 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or 16-bit float or 32-bit float or 64-bit float or Secret of 16-bit float or 32-bit float or 64-bit float values or complex type with 32-bit float or 64-bit float elements or Secret of complex type with 32-bit float or 64-bit float elements values values
| `result` | statically shaped tensor of 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values values

### `pphlo.prefer_a` (spu::pphlo::PreferAOp)
### `pphlo.power` (spu::pphlo::PowOp)

_Prefer AShare operator_
_Power operator_


Syntax:

```
operation ::= `pphlo.prefer_a` $operand attr-dict `:` custom<SameOperandsAndResultType>(type($operand), type($result))
operation ::= `pphlo.power` $lhs `,` $rhs attr-dict
`:` custom<SameOperandsAndResultType>(type($lhs), type($rhs), type($result))
```

Convert input to AShare if possible.
Performs element-wise exponentiation of `lhs` tensor by `rhs` tensor and produces a `result` tensor.

Traits: `AlwaysSpeculatableImplTrait`, `Elementwise`, `SameOperandsAndResultShape`, `SameOperandsAndResultType`
Ref https://github.com/openxla/stablehlo/blob/main/docs/spec.md#power

Traits: `AlwaysSpeculatableImplTrait`, `Elementwise`, `SameOperandsAndResultShape`

Interfaces: `ConditionallySpeculatable`, `InferShapedTypeOpInterface`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`

Expand All @@ -1684,7 +1692,8 @@ Effects: `MemoryEffects::Effect{}`

| Operand | Description |
| :-----: | ----------- |
| `operand` | statically shaped tensor of pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values or 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or 16-bit float or 32-bit float or 64-bit float or Secret of 16-bit float or 32-bit float or 64-bit float values or complex type with 32-bit float or 64-bit float elements or Secret of complex type with 32-bit float or 64-bit float elements values values
| `lhs` | statically shaped tensor of pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values or 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or 16-bit float or 32-bit float or 64-bit float or Secret of 16-bit float or 32-bit float or 64-bit float values or complex type with 32-bit float or 64-bit float elements or Secret of complex type with 32-bit float or 64-bit float elements values values
| `rhs` | statically shaped tensor of pred (AKA boolean or 1-bit integer) or Secret of 1-bit signless integer values or 8/16/32/64-bit signless integer or Secret of 8-bit signless integer or 16-bit signless integer or 32-bit signless integer or 64-bit signless integer values or 8/16/32/64-bit unsigned integer or Secret of 8/16/32/64-bit unsigned integer values or 16-bit float or 32-bit float or 64-bit float or Secret of 16-bit float or 32-bit float or 64-bit float values or complex type with 32-bit float or 64-bit float elements or Secret of complex type with 32-bit float or 64-bit float elements values values

#### Results:

Expand Down Expand Up @@ -2270,12 +2279,21 @@ Returns the sign of the `operand` element-wise and produces a `result` tensor.
Ref https://github.com/openxla/stablehlo/blob/main/docs/spec.md#sign
PPHLO Extension: when `ignore_zero` is set to true, sign does not enforce sign(0) to 0
Traits: `AlwaysSpeculatableImplTrait`, `Elementwise`, `SameOperandsAndResultShape`, `SameOperandsAndResultType`
Interfaces: `ConditionallySpeculatable`, `InferShapedTypeOpInterface`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`
Effects: `MemoryEffects::Effect{}`
#### Attributes:
<table>
<tr><th>Attribute</th><th>MLIR Type</th><th>Description</th></tr>
<tr><td><code>ignore_zero</code></td><td>::mlir::BoolAttr</td><td>bool attribute</td></tr>
</table>
#### Operands:
| Operand | Description |
Expand Down Expand Up @@ -2377,7 +2395,7 @@ Ref https://github.com/openxla/stablehlo/blob/main/docs/spec.md#slice
Traits: `AlwaysSpeculatableImplTrait`, `SameOperandsAndResultElementType`
Interfaces: `ConditionallySpeculatable`, `NoMemoryEffect (MemoryEffectOpInterface)`
Interfaces: `ConditionallySpeculatable`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`
Effects: `MemoryEffects::Effect{}`
Expand Down Expand Up @@ -2551,7 +2569,7 @@ Ref https://github.com/openxla/stablehlo/blob/main/docs/spec.md#transpose
Traits: `AlwaysSpeculatableImplTrait`, `SameOperandsAndResultElementType`
Interfaces: `ConditionallySpeculatable`, `NoMemoryEffect (MemoryEffectOpInterface)`
Interfaces: `ConditionallySpeculatable`, `InferTypeOpInterface`, `NoMemoryEffect (MemoryEffectOpInterface)`
Effects: `MemoryEffects::Effect{}`
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/runtime_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ The SPU runtime configuration.
| Field | Type | Description |
| ----- | ---- | ----------- |
| server_host | [ string](#string) | TrustedThirdParty beaver server's remote ip:port or load-balance uri. |
| session_id | [ string](#string) | if empty, use link id as session id. |
| adjust_rank | [ int32](#int32) | which rank do adjust rpc call, usually choose the rank closer to the server. |
| asym_crypto_schema | [ string](#string) | asym_crypto_schema: support ["SM2"] Will support 25519 in the future, after yacl supported it. |
| server_public_key | [ bytes](#bytes) | server's public key |
<!-- end Fields -->
<!-- end HasFields -->

Expand Down
4 changes: 2 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
myst-parser==3.0.1
rstcheck==6.2.1
sphinx==7.3.7
rstcheck==6.2.4
sphinx==8.0.2
nbsphinx==0.9.4
sphinx-autobuild==2024.4.16
sphinx-markdown-parser==0.2.4
Expand Down
2 changes: 1 addition & 1 deletion examples/python/ml/jax_lr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This example demonstrates how to use SPU to train a logistic regression model pr
1. Launch SPU backend runtime

```sh
bazel run -c opt //examples/python/utils:nodectl -- up
bazel run -c opt //examples/python/utils:nodectl -- -c examples/python/conf/2pc_semi2k.json up
```

2. Run `jax_lr` example
Expand Down
17 changes: 15 additions & 2 deletions experimental/squirrel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,24 @@ Code under this folder is purely for research demonstration and it's **NOT desig
* On one terminal

```sh
bazel-bin/experimental/squirrel/squirrel_demo_main --rank0_nfeatures=85 --rank1_nfeatures=85 --standalone --train=BinaryClassification_Aps_Test_60000_171.csv --test=BinaryClassification_Aps_Test_16000_171.csv --standalone=true --rank=0 --has_label=0 --lr=1.0 --subsample=0.8
bazel-bin/experimental/squirrel/squirrel_demo_main --rank0_nfeatures=85 --rank1_nfeatures=85 --standalone=true --train=BinaryClassification_Aps_Test_60000_171.csv --test=BinaryClassification_Aps_Test_16000_171.csv --rank=0 --has_label=0 --lr=1.0 --subsample=0.8
```

* On another terminal

```sh
bazel-bin/experimental/squirrel/squirrel_demo_main --rank0_nfeatures=85 --rank1_nfeatures=85 --standalone --train=BinaryClassification_Aps_Test_60000_171.csv --test=BinaryClassification_Aps_Test_16000_171.csv --standalone=true --rank=1 --has_label=1 --lr=1.0 --subsample=0.8
bazel-bin/experimental/squirrel/squirrel_demo_main --rank0_nfeatures=85 --rank1_nfeatures=85 --standalone=true --train=BinaryClassification_Aps_Test_60000_171.csv --test=BinaryClassification_Aps_Test_16000_171.csv --rank=1 --has_label=1 --lr=1.0 --subsample=0.8
```
* Run on distributed dataset, e.g., using the `breast_cancer` dataset from the SPU repo.
* On one terminal

```sh
bazel-bin/experimental/squirrel/squirrel_demo_main --rank0_nfeatures=15 --rank1_nfeatures=15 --standalone=false --train=examples/data/breast_cancer_a.csv --rank=0 --has_label=0 --lr=1.0 --subsample=0.8
```

* On another terminal

```sh
bazel-bin/experimental/squirrel/squirrel_demo_main --rank0_nfeatures=15 --rank1_nfeatures=15 --standalone=false --train=examples/data/breast_cancer_b.csv --rank=1 --has_label=1 --lr=1.0 --subsample=0.8
```

9 changes: 6 additions & 3 deletions experimental/squirrel/bin_matvec_prot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -766,9 +766,12 @@ spu::NdArrayRef BinMatVecProtocol::Recv(const spu::NdArrayRef &vec_in,
SPU_ENFORCE_EQ(vec_in.numel(), dim_in);
if (not indicator.empty()) {
// mat * diag(indicator) should not be all zeros.
SPU_ENFORCE(std::any_of(indicator.begin(), indicator.end(),
[](uint8_t x) { return x > 0; }),
"empty matrix is not allowed");
if (std::all_of(indicator.begin(), indicator.end(),
[](uint8_t x) { return x == 0; })) {
SPDLOG_WARN(
"Empty matrix! Make sure the 1-bit error will not ruin your "
"computation.");
}
}

auto eltype = vec_in.eltype();
Expand Down
5 changes: 4 additions & 1 deletion experimental/squirrel/bin_matvec_prot.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ struct StlSparseMatrix {
// Outputs:
// Sender: z0 \in Zk^{m}
// Recv: z1 \in Zk^{m}
// such that z0 + z1 = M * (v0 + v1) mod Zk
// such that z0 + z1 = M * (v0 + v1) + e mod Zk
//
// Note that we might introduce 1-bit error `e` due to the coefficient-based
// resharing HE ciphertexts to additive shares.
class BinMatVecProtocol {
public:
BinMatVecProtocol(size_t ring_bitwidth,
Expand Down
Loading

0 comments on commit 597a0b6

Please sign in to comment.