Intelで__m256d型のデータをシリアライズし、WebSocketで転送して、RISC-Vでデシリアライズして結果を表示 (IntelのIntrinsic関数をRISC-Vにポートする)

# Intelで__m256d型のデータをシリアライズし、WebSocketで転送して、RISC-Vでデシリアライズして結果を表示 (IntelのIntrinsic関数をRISC-Vにポートする)

この記事は、DockerとDocker Composeを使用して、Intelプラットフォーム（linux/amd64）とRISC-Vプラットフォーム（linux/riscv64）のUbuntu 22.04イメージを起動し、WebSocketを介して通信する方法を説明します。Intelプラットフォームでは__m256d型のデータをシリアライズし、RISC-Vプラットフォームでデシリアライズして表示します。

## 必要なもの
- Docker
- Docker Compose

## 手順

### 1. プロジェクト構成

まず、プロジェクトのディレクトリ構造を作成します。

```bash
mkdir websocket_project
cd websocket_project
mkdir intel riscv
```

### 2. Intelプラットフォーム用のコード

`intel`ディレクトリに移動し、WebSocketクライアントとシリアライズコードを作成します。

#### 2.1 `Dockerfile`

```Dockerfile
# intel/Dockerfile
FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install -y \
    build-essential \
    libwebsockets-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

COPY . /app
WORKDIR /app

RUN gcc -o ws_client ws_client.c -lwebsockets -lm -mavx

CMD ["./ws_client"]
```

#### 2.2 `ws_client.c`

以下の内容でファイルを作成します。

```c
#include <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <immintrin.h>

#define BUFFER_SIZE 32

struct per_session_data {
    uint8_t buffer[BUFFER_SIZE];
    size_t len;
};

static int callback_websockets(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
    struct per_session_data *psd = (struct per_session_data *)user;

    switch (reason) {
        case LWS_CALLBACK_CLIENT_ESTABLISHED:
            lwsl_user("Connection established\n");

            // __m256dデータのシリアライズ
            __m256d vec = _mm256_set_pd(4.0, 3.0, 2.0, 1.0);
            _mm256_storeu_pd((double *)psd->buffer, vec);
            psd->len = BUFFER_SIZE;

            lws_callback_on_writable(wsi);
            break;

        case LWS_CALLBACK_CLIENT_WRITEABLE:
            if (psd->len > 0) {
                lws_write(wsi, psd->buffer, psd->len, LWS_WRITE_BINARY);
                psd->len = 0;
            }
            break;

        case LWS_CALLBACK_CLIENT_RECEIVE:
            // 受信したデータの処理（必要なら）
            break;

        case LWS_CALLBACK_CLIENT_CLOSED:
            lwsl_user("Connection closed\n");
            break;

        default:
            break;
    }

    return 0;
}

static struct lws_protocols protocols[] = {
    {
        .name = "ws-protocol",
        .callback = callback_websockets,
        .per_session_data_size = sizeof(struct per_session_data),
        .rx_buffer_size = 0,
    },
    {NULL, NULL, 0, 0}
};

int main(void) {
    struct lws_context_creation_info info;
    memset(&info, 0, sizeof(info));

    info.port = CONTEXT_PORT_NO_LISTEN;
    info.protocols = protocols;

    struct lws_context *context = lws_create_context(&info);
    if (context == NULL) {
        lwsl_err("lws_create_context failed\n");
        return 1;
    }

    struct lws_client_connect_info ccinfo = {
        .context = context,
        .address = "riscv-server",
        .port = 8000,
        .path = "/",
        .protocol = protocols[0].name,
        .ssl_connection = 0,
        .host = lws_canonical_hostname(context),
        .origin = "origin",
        .ietf_version_or_minus_one = -1,
    };

    struct lws *wsi = lws_client_connect_via_info(&ccinfo);
    if (wsi == NULL) {
        lwsl_err("lws_client_connect_via_info failed\n");
        lws_context_destroy(context);
        return 1;
    }

    while (lws_service(context, 1000) >= 0) {
        // イベントループ
    }

    lws_context_destroy(context);
    return 0;
}
```

### 3. RISC-Vプラットフォーム用のコード

`riscv`ディレクトリに移動し、WebSocketサーバーとデシリアライズコードを作成します。

#### 3.1 `Dockerfile`

```Dockerfile
# riscv/Dockerfile
FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install -y \
    build-essential \
    libwebsockets-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

COPY . /app
WORKDIR /app

RUN gcc -o ws_server ws_server.c -lwebsockets

CMD ["./ws_server"]
```

#### 3.2 `ws_server.c`

以下の内容でファイルを作成します。

```c
#include <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <riscv_vector.h>

#define BUFFER_SIZE 32

struct per_session_data {
    uint8_t buffer[BUFFER_SIZE];
    size_t len;
};

static int callback_websockets(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
    struct per_session_data *psd = (struct per_session_data *)user;

    switch (reason) {
        case LWS_CALLBACK_ESTABLISHED:
            lwsl_user("Connection established\n");
            break;

        case LWS_CALLBACK_RECEIVE:
            if (len == BUFFER_SIZE) {
                memcpy(psd->buffer, in, len);
                psd->len = len;

                // デシリアライズ
                vfloat64m1_t vec = vle64_v_f64m1((double *)psd->buffer, 4);

                double result_data[4];
                vse64_v_f64m1(result_data, vec, 4);

                // デシリアライズ結果の表示
                for (int i = 0; i < 4; ++i) {
                    printf("result[%d] = %f\n", i, result_data[i]);
                }
            }
            break;

        case LWS_CALLBACK_CLOSED:
            lwsl_user("Connection closed\n");
            break;

        default:
            break;
    }

    return 0;
}

static struct lws_protocols protocols[] = {
    {
        .name = "ws-protocol",
        .callback = callback_websockets,
        .per_session_data_size = sizeof(struct per_session_data),
        .rx_buffer_size = 0,
    },
    {NULL, NULL, 0, 0}
};

int main(void) {
    struct lws_context_creation_info info;
    memset(&info, 0, sizeof(info));

    info.port = 8000;
    info.protocols = protocols;

    struct lws_context *context = lws_create_context(&info);
    if (context == NULL) {
        lwsl_err("lws_create_context failed\n");
        return 1;
    }

    while (lws_service(context, 1000) >= 0) {
        // イベントループ
    }

    lws_context_destroy(context);
    return 0;
}
```

### 4. Docker Composeファイル

プロジェクトのルートディレクトリに移動し、`docker-compose.yml`ファイルを作成します。

```yaml
version: '3.8'

services:
  intel-client:
    build:
      context: ./intel
      dockerfile: Dockerfile
    platform: linux/amd64
    depends_on:
      - riscv-server

  riscv-server:
    build:
      context: ./riscv
      dockerfile: Dockerfile
    platform: linux/riscv64
    ports:
      - "8000:8000"
```

### 5. プロジェクトのビルドと起動

プロジェクトのルートディレクトリで、以下のコマンドを実行してDocker Composeでプロジェクトをビルドし、起動します。

```bash
docker-compose up --build
```

### 実行結果

以下は、`docker-compose up` コマンドを実行した際の出力結果の例です。

```plaintext
Creating network "websocket_project_default" with the default driver
Building riscv-server
Step 1/7 : FROM ubuntu:22.04
...
Successfully built abcdef123456
Successfully tagged websocket_project_riscv-server:latest
Building intel-client
Step 1/7 : FROM ubuntu

:22.04
...
Successfully built 123456abcdef
Successfully tagged websocket_project_intel-client:latest
Creating websocket_project_riscv-server_1 ... done
Creating websocket_project_intel-client_1 ... done
Attaching to websocket_project_riscv-server_1, websocket_project_intel-client_1
riscv-server_1  | [2024/06/28 12:34:56:1234] USER: Connection established
intel-client_1  | [2024/06/28 12:34:56:5678] USER: Connection established
riscv-server_1  | result[0] = 1.000000
riscv-server_1  | result[1] = 2.000000
riscv-server_1  | result[2] = 3.000000
riscv-server_1  | result[3] = 4.000000
intel-client_1  | [2024/06/28 12:34:56:5678] USER: Connection closed
riscv-server_1  | [2024/06/28 12:34:56:1234] USER: Connection closed
```

### まとめ

DockerとDocker Composeを使用して、Intelプラットフォーム（linux/amd64）とRISC-Vプラットフォーム（linux/riscv64）間でWebSocketを介したデータ通信を実現しました。Intelプラットフォームでは__m256d型のデータをシリアライズし、RISC-Vプラットフォームでデシリアライズして結果を表示しました。これにより、異なるアーキテクチャ間でのデータ通信が可能となります。