Assembly – DECODE https://decode.red/blog data decode, decoder or decoded ... design of code Mon, 15 Dec 2025 06:15:00 +0000 ja hourly 1 https://wordpress.org/?v=4.7.29 WebAssembly Runtime ../../../202307231666/ Sun, 23 Jul 2023 06:48:46 +0000 ../../../?p=1666 wasm(WebAssembly)のランタイムについて、いろいろ調べてみました。
WebAssemblyは何度も扱ってきていますが、WATコード(WebAssembly Text)からコンパイルを試したことがありませんでした。まずは基本を押さえたいと思います。(参考:翔泳社「入門WebAssembly」)

環境) M2 Mac

myadd.wat

(module
	(func (export "myadd")
		(param $value.1 i32) (param $value.2 i32)
		(result i32)
		local.get $value.1
		local.get $value.2
		i32.add
	)
)

value 1,2をスタックに積んで、関数呼び出し。まさにアセンブラですね。

myadd.js

const fs = require('fs');

const bytes = fs.readFileSync(__dirname + '/myadd.wasm');
const v1 = parseInt(process.argv[2]);
const v2 = parseInt(process.argv[3]);

(async () => {
	const obj = await WebAssembly.instantiate(new Uint8Array(bytes));
	let ret = obj.instance.exports.myadd(v1, v2);
	console.log("%d + %d = %d", v1, v2, ret);
})();

% node -v
v18.12.1
% npm -v
8.19.2
% npm install -g wat-wasm

% wat2wasm myadd.wat

  Contact Rick Battagline
  Twitter: @battagline
  https://wasmbook.com
  v1.0.43

no memory
Writing to myadd.wasm
WASM File Saved!

% node myadd.js 1 2
1 + 2 = 3

wat2wasmで、バイトコードに、そしてそれをnode.jsから呼び出しました。

次は、node.jsではなく、wasmtimeというランタイムを使って実行してみます。(brew でインストール)

引用元)https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md

demo.wat

(module
    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
    ;; The function signature for fd_write is:
    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))

    ;; Write 'hello world\n' to memory at an offset of 8 bytes
    ;; Note the trailing newline which is required for the text to appear
    (data (i32.const 8) "hello world\n")

    (func $main (export "_start")
        ;; Creating a new io vector within linear memory
        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string

        (call $fd_write
            (i32.const 1) ;; file_descriptor - 1 for stdout
            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
        )
        drop ;; Discard the number of bytes written from the top of the stack
    )
)

% wasmtime -V
wasmtime-cli 10.0.1
% wasmtime demo.wat
hello world
% wat2wasm demo.wat

  Contact Rick Battagline
  Twitter: @battagline
  https://wasmbook.com
  v1.0.43

memory found
Writing to demo.wasm
WASM File Saved!
% wasmtime demo.wasm
hello world

先ほどのWATコードでは、実行はできますが標準出力に書かないためコードを変えています。
(watから直接実行もできるもよう)

ファイル確認

% file demo.wasm
demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
% file myadd.wasm
myadd.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

次は、Rustのコードからコンパイルするため、環境をdockerで構築しました。
引用元) https://qiita.com/toshikisugiyama/items/168cdbf834156b1f1e70

docker-compose.yml

services:
  rust:
    image: rust:latest
    volumes:
      - .:/projects
    working_dir: /projects
    environment:
      - USER=user

% docker compose run –rm rust

このコマンドで一気に環境を作り、コンテナのシェルの状態になります。

root@f4b90a7fb5a9:/projects# cargo new hello_cargo
  Created binary (application) hello_cargo package

root@f4b90a7fb5a9:/projects# ls
docker-compose.yml hello_cargo

root@f4b90a7fb5a9:/projects# cd hello_cargo/
root@f4b90a7fb5a9:/projects/hello_cargo# ls
Cargo.toml src

root@f4b90a7fb5a9:/projects/hello_cargo# cargo run
  Compiling hello_cargo v0.1.0 (/projects/hello_cargo)
  Finished dev [unoptimized + debuginfo] target(s) in 1.07s
  Running target/debug/hello_cargo
Hello, world!

デフオルトプロジェクトの状態で実行できることを確認できました。(これはwasmではない)

hello_cargo/src/main.rs

fn main() {
    println!("Hello, world!");
}

カレントフォルダとコンテナの/projectsが、マウントされているため、ファイルの編集は別コンソールで、できます。(コマンド実行だけコンテナからやればよいことにことに)

そして、この環境を使って、wasmtimeの例を動かしてみます。
引用元) https://wasmtime.dev/

root@f4b90a7fb5a9:/projects# rustup target add wasm32-wasi
info: downloading component ‘rust-std’ for ‘wasm32-wasi’
info: installing component ‘rust-std’ for ‘wasm32-wasi’
root@f4b90a7fb5a9:/projects# rustc hello.rs –target wasm32-wasi
root@f4b90a7fb5a9:/projects# wasmtime hello.wasm
bash: wasmtime: command not found

コンテナにはwasmtimeを入れていないので、カレントに戻り、

% wasmtime hello.wasm
Hello, world!

(hello.rsは、上記 hello_cargo/src/main.rs と全く同じ)
Rustコードをwasmにコンパイルして実行できることを確認しました。

そして次は、wasmをコンテナにして実行してみます。
引用元) https://developer.mamezou-tech.com/blogs/2023/01/25/using-wasm-on-docker/

% docker run -dp 8080:8080 –name=wasm-example –runtime=io.containerd.wasmedge.v1 –platform=wasi/wasm32 michaelirwin244/wasm-example
Unable to find image ‘michaelirwin244/wasm-example:latest’ locally
latest: Pulling from michaelirwin244/wasm-example
docker: operating system is not supported.
See ‘docker run –help’.

Use containerd for pulling and storing images -> on にします。

% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
029a5376af41 michaelirwin244/wasm-example “/hello_world.wasm” 14 seconds ago Up 9 seconds 0.0.0.0:8080->8080/tcp wasm-example
% docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
michaelirwin244/wasm-example latest 1b0714a5af55 37 seconds ago 4.14MB

% curl localhost:8080
Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to you%
% curl -X POST -d abcdefg localhost:8080
% curl -X POST -d abcdefg localhost:8080/echo
abcdefg%

うまく立ち上がり動作も確認できました。(POST /echo の場合はBodyをEcho)

でもこれではwasmかどうかわかりませんので、サーバの部分を単体で動かしてみます。
(引用元の情報を参考にダウンロード)

% git clone https://github.com/mikesir87/wasm-example.git

% cd wasm-example

コンテナでコマンド実行

root@bea36f5878ef:/projects/wasm-example# rustup target add wasm32-wasi
info: downloading component ‘rust-std’ for ‘wasm32-wasi’
info: installing component ‘rust-std’ for ‘wasm32-wasi’
root@bea36f5878ef:/projects/wasm-example# cargo build –target wasm32-wasi –release

カレントに戻りwasmtime実行

% wasmtime run –tcplisten 127.0.0.1:8080 target/wasm32-wasi/release/hello_world.wasm
Error: failed to run main module target/wasm32-wasi/release/hello_world.wasm

Caused by:
  0: failed to instantiate “target/wasm32-wasi/release/hello_world.wasm”
  1: unknown import: wasi_snapshot_preview1::sock_setsockopt has not been defined

うまく行かず。。。
Runtimeをwasmedgeに差し替えてチャレンジ。

% curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
Using Python: /Users/z/.pyenv/versions/miniforge3-4.10.3-10/bin/python3
INFO – Compatible with current configuration
INFO – Running Uninstaller
WARNING – Uninstaller did not find previous installation
WARNING – SHELL variable not found. Using zsh as SHELL
INFO – shell configuration updated
INFO – Downloading WasmEdge
|============================================================|100.00 %INFO – Downloaded
INFO – Installing WasmEdge
INFO – WasmEdge Successfully installed
INFO – Run:
source /Users/z/.zshenv

% wasmedge target/wasm32-wasi/release/hello_world.wasm
Server is now running

これで、さほどのPOST /echo のcurlアクセスもうまくいきました。(dockerのwasm runtimeはwasmtimeと違うようです)

wasm-example/src/main.rs

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, StatusCode, Server};
use std::convert::Infallible;
use std::net::SocketAddr;
use std::result::Result;

async fn handle_request(req: Request<Body>) -> Result<Response<Body>, anyhow::Error> {
    match (req.method(), req.uri().path()) {
        (&Method::GET, "/") => Ok(Response::new(Body::from(
            "Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to you",
        ))),

        // Simply echo the body back to the client.
        (&Method::POST, "/echo") => Ok(Response::new(req.into_body())),

        // Return the 404 Not Found for other routes.
        _ => {
            let mut not_found = Response::default();
            *not_found.status_mut() = StatusCode::NOT_FOUND;
            Ok(not_found)
        }
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
    let make_svc = make_service_fn(|_| {
        async move {
            Ok::<_, Infallible>(service_fn(move |req| {
                handle_request(req)
            }))
        }
    });
    let server = Server::bind(&addr).serve(make_svc);
    println!("Server is now running");
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
    Ok(())
}

ここまでwasmの動かし方について、いろいろ試してみました。前回Goを試しましたがいろいろ調べてみるとRustが非常に向いていると感じました。(まだ関数コールをやる必要がありますが、これは次の機会に・・)
下記サイトに、Goとwasmの関係について、とても参考になる記事があります。

「GoのWASMがライブラリではなくアプリケーションであること」
https://www.kabuku.co.jp/developers/annoying-go-wasm

RustはOSすらないベアメタル環境でもプログラムを動作させることができます。様々なところで実行できるwasmだけに、こういった成り立ちの違いが大きく影響するのではと感じました。

]]>
WebAssembly / Go ../../../202307161660/ Sun, 16 Jul 2023 01:27:39 +0000 ../../../?p=1660 Go言語が、WebAssemblyを標準でサポートしているということで、実際に動かしてみました。
ブラウザでJavaScript経由で呼び出します。

参考)
https://atmarkit.itmedia.co.jp/ait/articles/2305/19/news002.html

環境は前回のProtocolBufferと同じで、Go は、v1.20.5 です。

前準備

mkdir go_wasm
cd go_wasm
go mod init sample
go get github.com/shurcooL/goexec
go install github.com/shurcooL/goexec@latest

main.go (用意するのはこれだけ)

package main
import "fmt"
func main() {
    fmt.Println("Hello, WebAssembly!")
}

コンパイル

GOOS=js GOARCH=wasm go build -o test.wasm main.go

ファイル確認

% file test.wasm
test.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

Webで実行させるために生成されたファイルをカレントにコピー
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
cp $(go env GOROOT)/misc/wasm/wasm_exec.html .

WebServerの起動

~/go/bin/goexec ‘http.ListenAndServe(:8080, http.FileServer(http.Dir(.)))’

実行結果

参考サイトそのままですが、それだけ何もいじることをしなくても良い、というシンプルな仕組みになっています。
miscフォルダにrunボタンでwasmを実行するjsとhtmlを生成することは、至れり尽くせりだと思いました。

wasmは、バイナリでありながら様々な場所で実行できるため、いろいろな使われ方をします。またGoも様々な使われ方を考えたいろいろな機能を持っています。今回はとりあえず、やってみた、という感じですが、次はデータのやり取りなどやってみようと思っています。

以下にhtmlを引用します。jsはサイスが大きいためとりやめ。
wasm_exec.html

<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>

<head>
        <meta charset="utf-8">
        <title>Go wasm</title>
</head>

<body>
        <!--
        Add the following polyfill for Microsoft Edge 17/18 support:
        <script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
        (see https://caniuse.com/#feat=textencoder)
        -->
        <script src="wasm_exec.js"></script>
        <script>
                if (!WebAssembly.instantiateStreaming) { // polyfill
                        WebAssembly.instantiateStreaming = async (resp, importObject) => {
                                const source = await (await resp).arrayBuffer();
                                return await WebAssembly.instantiate(source, importObject);
                        };
                }

                const go = new Go();
                let mod, inst;
                WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
                        mod = result.module;
                        inst = result.instance;
                        document.getElementById("runButton").disabled = false;
                }).catch((err) => {
                        console.error(err);
                });

                async function run() {
                        console.clear();
                        await go.run(inst);
                        inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
                }
        </script>

        <button onClick="run();" id="runButton" disabled>Run</button>
</body>

</html>

]]>
64bit register ../../../202210081499/ Sat, 08 Oct 2022 05:06:30 +0000 ../../../?p=1499 久しぶりにx64アセンブラねたです。あるプロジェクトで組み込み機器の処理高速化の際に思いついたのがレジスタを使った高速繰り返しコピーです。あるバイト列を繰り返してコピーするとき、メモリとレジスタを行き来してコピーしていたものをレジスタのみでコピーすることによりパフォーマンスアップできます。
64bit CPUともなるとレジスタも64bit(8byte)になり、下記はこれを二つ使って16バイトデータを4回繰り返しコピーするプログラムです。(後半8バイトは0)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <memory.h>

long int duplicate(unsigned char *src, unsigned char *dst, long int n)
{
 long int ret;
__asm__ __volatile__ (
 "movq  (%2), %%rax\n"
 "movq  8(%2), %%rdx\n"
 "leaq  (%3), %%rbx\n"
 "movq  %1, %%rcx\n"
 "start:\n"
 "movq  %%rax, (%%rbx)\n"
 "movq  %%rdx, 8(%%rbx)\n"
 "addq  $16, %%rbx\n"
 "loop  start;"
:"=r" (ret)
:"r"(n), "r"(src), "r"(dst)
:"%rcx", "%rax", "%rdx", "%rbx", "memory"
);

return ret;
}

unsigned char data1[100], data2[100];

int main()
{
        memset(data1, 0, 100);
        for(int i=0;i<8;i++){
                data1[i] = 0x41 + i;
        }
        memset(data2, 0, 100);
        duplicate(data1, data2, 4);
        for(int i=0;i<16*4;i++){
                printf("%02x", data2[i]);
                if(i%16==15) printf("\n");
        }
}

実行結果

備忘録でした。

]]>
Web Assembly (3) ../../../202205011437/ Sun, 01 May 2022 00:29:41 +0000 ../../../?p=1437 Emscripten SDKを使って、WebブラウザでWeb Assemblyを実行してみました。

参考)
https://emscripten.org/docs/getting_started/downloads.html

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

参考)
https://developer.mozilla.org/ja/docs/WebAssembly/C_to_wasm

#include <stdio.h>
#include <emscripten/emscripten.h>

int main(int argc, char ** argv) {
    //printf("Hello World\n");
    printf("Hello World : %d : %s\n", argc, argv[0]);
}

#ifdef __cplusplus
extern "C" {
#endif
/*
void EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {
  printf("MyFunction Called %d: \n",argc);
  //printf("MyFunction Called %d: %s\n",argc,argv[0]);
}
*/
int EMSCRIPTEN_KEEPALIVE myFunction(int p1, int p2) {

        return p1 + p2;
}
#ifdef __cplusplus
}
#endif

ドキュメントにあるCの参考コードに、関数の入力と出力を加えました。

emcc hello3.c -s WASM=1 -o hello3.html -s NO_EXIT_RUNTIME=1 -s “EXTRA_EXPORTED_RUNTIME_METHODS=[‘ccall’]”

このコマンドで .html, .jsを自動生成してくれます。

........
  </script>
    <script async type="text/javascript" src="hello3.js">


</script>
<!--<button class="mybutton">Run myFunction</button> -->
<button id="mybutton">Run myFunction</button>

<script type="text/javascript">

document.getElementById('mybutton').addEventListener('click', function(){
  alert('check console');
  var result = Module.ccall('myFunction', // name of C function
                             'number', //null, // return type
                             ['number', 'number'], //null, // argument types
                             [10, 20]); //null); // arguments
console.log(result);
});
</script>

  </body>
</html>

.htmlファイルを上記のように変更します。

python3 -m http.server

Pythonの簡易 Webサーバを使って .htmlファイルを読み込みます。

実行結果

ボタンをクリックすると足し算の結果をコンソールに表示します。

Emscripten 便利です。

]]>
Web Assembly (2) ../../../202204021431/ Sat, 02 Apr 2022 07:42:45 +0000 ../../../?p=1431 前回とは違って、コンソールでビルドしてみました。WebAssembly System Interface のTurtorial を使います。

https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md

とても重要なコードなので、CとRustそれぞれ引用させていただきました。

demo.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char **argv) {
    ssize_t n, m;
    char buf[BUFSIZ];

    if (argc != 3) {
        fprintf(stderr, "usage: %s <from> <to>\n", argv[0]);
        exit(1);
    }

    int in = open(argv[1], O_RDONLY);
    if (in < 0) {
        fprintf(stderr, "error opening input %s: %s\n", argv[1], strerror(errno));
        exit(1);
    }

    int out = open(argv[2], O_WRONLY | O_CREAT, 0660);
    if (out < 0) {
        fprintf(stderr, "error opening output %s: %s\n", argv[2], strerror(errno));
        exit(1);
    }

    while ((n = read(in, buf, BUFSIZ)) > 0) {
        char *ptr = buf;
        while (n > 0) {
            m = write(out, ptr, (size_t)n);
            if (m < 0) {
                fprintf(stderr, "write error: %s\n", strerror(errno));
                exit(1);
            }
            n -= m;
            ptr += m;
        }
    }

    if (n < 0) {
        fprintf(stderr, "read error: %s\n", strerror(errno));
        exit(1);
    }

    return EXIT_SUCCESS;
}

main.rs

use std::env;
use std::fs;
use std::io::{Read, Write};

fn process(input_fname: &str, output_fname: &str) -> Result<(), String> {
    let mut input_file =
        fs::File::open(input_fname).map_err(|err| format!("error opening input {}: {}", input_fname, err))?;
    let mut contents = Vec::new();
    input_file
        .read_to_end(&mut contents)
        .map_err(|err| format!("read error: {}", err))?;

    let mut output_file = fs::File::create(output_fname)
        .map_err(|err| format!("error opening output {}: {}", output_fname, err))?;
    output_file
        .write_all(&contents)
        .map_err(|err| format!("write error: {}", err))
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    if args.len() < 3 {
        eprintln!("usage: {} <from> <to>", program);
        return;
    }

    if let Err(err) = process(&args[1], &args[2]) {
        eprintln!("{}", err)
    }
}

Cのビルド

https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-14

よりSDKをダウンロードしてビルド

wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-14/wasi-sdk-14.0-linux.tar.gz
tar zxvf wasi-sdk-14.0-linux.tar.gz
./wasi-sdk-14.0/bin/clang –sysroot=./wasi-sdk-14.0/share/wasi-sysroot demo.c -o demo.wasm
file demo.wasm
demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

cppの場合は、clang++ を使用

Rustのビルド

rustup target add wasm32-wasi
cargo build –target wasm32-wasi

target/wasm32-wasi/debug にdemo.wasmが出力される

wasmtimeインストール(sourceコマンドでパス有効化)

curl http://wasmtime.dev/install.sh -sSf | bash

実行結果

$ echo hello > hello.txt
$ wasmtime demo.wasm
usage: demo.wasm [from] [to]
$ wasmtime –dir=. –dir=. demo.wasm hello.txt out.txt
$ cat out.txt
hello

wasmtimeはWASIのランタイムで、wasmファイルを直接実行できます。一見普通のコードとWasmは区別がつかないくらいです。
Cの場合、環境がきちんと通っていないと間違えて通常のバイナリを作ってしまいます。
wasmは一つのバイナリをいろいろなところで使うことができます。下記動画が参考になります。
(しかし古いせいか作ったモジュールが不正となってしまい動作確認がとれませんでした)

“WebAssembly Interface Types: Interoperate with All The Things!”

]]>
Web Assembly ../../../202203121423/ Sat, 12 Mar 2022 08:04:55 +0000 ../../../?p=1423 アセンブラといえば、高級言語では記述できないようなことを直接コーディングしてプログラムを最適化したりできますが、これまで私は主にインテルCPUでC言語ベースの開発で補助的に使ってきました。このWeb版ともいえるのがWebAssemblyということですが、最近とてもよく見かけるようになりましたので、まずはとっかかりの良い便利なWebAssembly Studio というツールから試したいと思います。

https://github.com/wasdk/WebAssemblyStudio

https://webassembly.studio/ このURLでプレイグランドのようなものから立ち上がると思っていたのですが、リンク切れのようですので、git cloneしてビルドしました。

npm install
npm run dev-server

ブラウザでhttps://localhost:28443/にアクセスすると、作成するプロジェクトの選択画面がでてきますが、まずはC言語を選びました。

main.c, main.html, main.js を以下のように編集して、”Build & Run”をクリックします。(main.wasmを出力。主な変更点は、関数呼び出しのI/Fを確認するadd()を追加)
すると右下に画面が表示されます。

main.wasmは右クリックで様々な形式に変換できます。

下記動画には、まだ様々な機能が紹介されていますが、ひきつづき学習したいと思います。

参考動画

]]>
Return Value ../../../20190425981/ Thu, 25 Apr 2019 12:44:29 +0000 ../../../?p=981 アセンブリ言語で、C言語の戻り値がどのように記述されているか、調べてみました。理由は、最近returnを書き忘れるミスをしてしまったのに、プログラムが正常に動いていることに驚いたからです。

環境: Ubuntu 18.04

#include<stdio.h>

int func()
{
	int a = 3;
	int b = 5;
	int c = a + b;
}
int main()
{
	printf("ret:%d\n", func());
}

このプログラム、func関数の最後に” return c; “の記述が必要になりますが、これがあるときのアセンブラが左、ないときが右になります。
(cc -S test.c でアセンブラを出力できます)


コードを見ていくと、演算する数値をスタックに積んで、レジスタに移動してから演算をし、その結果をレジスタに書き込みます。関数から抜ける前にレジスタに演算結果を書き込むか書き込まないかの違いのみあります。演算結果を書き込まなくても、func関数の結果を利用するときたまたま同じレジスタに値があるので、結果が同じになった、ということのようです。

#include<stdio.h>

int func()
{
	int c = 8;
}
int main()
{
	printf("ret:%d\n", func());
}

当然ですが、上のような例では下のようなコードになり、値が正しく表示されません。

Rubyのような言語では、最後に演算した結果が戻り値になりますが、Cも? と一瞬戸惑いました。

長年プログラマをやってきましたが、こんな基本的なことに戸惑うとは・・まだまだです。
アセンブラを久しぶりに見る機会になったのはよかったかも。。

関連記事

Tail Recursion

]]>
Tail Recursion ../../../20161022617/ Sat, 22 Oct 2016 14:28:29 +0000 ../../../?p=617 末尾再帰の最適化でマシン語に久しぶりに触れてみようと思いました。マシン語を見ているとプログラムというのは、CPUに渡すデータである、とも感じとれます。
末尾再帰については、下記ブログで5年くらい前に取り上げました。

http://bitlife.me/archives/date/2011/11

これをLinuxでテストし、バイナリーエディットもしてみました。

環境: Ubuntu 16.04 LTS
recursion.c

#include<stdio.h>
void loop(int x)
{
        printf("%d ", x);
        loop(x + 1);
}
int main()
{
        loop(1);
}

cc -o recursion recursion.c
cc -o recursion_t -O2 recursion.c

recursion_tのように最適化すると、関数の再帰呼び出しがアドレスジャンプになります。

cc -S recursion.c
cc -O2 -S -o recursion_t.s recursion.c

recursion.s

.file   "recursion.c"
        .section        .rodata
.LC0:
        .string "%d "
        .text
        .globl  loop
        .type   loop, @function
loop:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movl    -4(%rbp), %eax
        movl    %eax, %esi
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        movl    -4(%rbp), %eax
        addl    $1, %eax
        movl    %eax, %edi
        call    loop
        nop
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   loop, .-loop
        .globl  main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $1, %edi
        call    loop
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits

recursion_t.s

.file   "recursion.c"
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "%d "
        .section        .text.unlikely,"ax",@progbits
.LCOLDB1:
        .text
.LHOTB1:
        .p2align 4,,15
        .globl  loop
        .type   loop, @function
loop:
.LFB23:
        .cfi_startproc
        pushq   %rbx
        .cfi_def_cfa_offset 16
        .cfi_offset 3, -16
        movl    %edi, %ebx
        .p2align 4,,10
        .p2align 3
.L2:
        movl    %ebx, %edx
        movl    $.LC0, %esi
        movl    $1, %edi
        xorl    %eax, %eax
        addl    $1, %ebx
        call    __printf_chk
        jmp     .L2
        .cfi_endproc
.LFE23:
        .size   loop, .-loop
        .section        .text.unlikely
.LCOLDE1:
        .text
.LHOTE1:
        .section        .text.unlikely
.LCOLDB2:
        .section        .text.startup,"ax",@progbits
.LHOTB2:
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB24:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        movl    $1, %edi
        call    loop
        .cfi_endproc
.LFE24:
        .size   main, .-main
        .section        .text.unlikely
.LCOLDE2:
        .section        .text.startup
.LHOTE2:
        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits

末尾再帰が最適化されているものはprintfの後に関数呼び出し(call)がなくなっているのがわかります。通常の再帰だと実行時スタックがオーバーフローして以下のようにエラーとなります。

./recursion
…….(省略)
24 261725 261726 261727 261728 261729 261730 261731 261732 261733 261734 261735 261736 261737 261738 261739 261740 261741 261742 261743 261744 261745 261746 261747 261748 261749 261750 261751 261752 261753 261754 261755 261756 261757 261758 261759 261760 261761 261762 261763 261764 261765 261766 261767 261768 261769 261770 261771 261772 261773 261774 261775 261776 261777 261778 261Segmentation fault (コアダンプ)

最適化されるとアドレスジャンプとなり無限にループします。

./recursion_t
…….(省略)
1968219 1968220 1968221 1968222 1968223 1968224 1968225 1968226 1968227 1968228 1968229 1968230 1968231 1968232 1968233 1968234 1968235 1968236 1968237 1968238 1968239 1968240 1968241 1968242 1968243 1968244 1968245 1968246 1968247 1968248 1968249 1968250 1968251 1968252 1968253 1968254 1968255 1968256 1968257 1968258 1968259 1968260 1968261 1968262 1968263 1968264 ^C
(無限につづく)

さてここでちょっと遊んでコアダンプする原因となる関数呼び出しをバイナリエディットして無効にしてみたいと思います。

cp recursion recursion_e
objdump -D recursion > recursion.dmp
vi -b recursion_e
objdump -D recursion_e > recursion_e.dmp

recursion.dmp抜粋

0000000000400526 <loop>:
  400526:       55                      push   %rbp
  400527:       48 89 e5                mov    %rsp,%rbp
  40052a:       48 83 ec 10             sub    $0x10,%rsp
  40052e:       89 7d fc                mov    %edi,-0x4(%rbp)
  400531:       8b 45 fc                mov    -0x4(%rbp),%eax
  400534:       89 c6                   mov    %eax,%esi
  400536:       bf f4 05 40 00          mov    $0x4005f4,%edi
  40053b:       b8 00 00 00 00          mov    $0x0,%eax
  400540:       e8 bb fe ff ff          callq  400400 <printf@plt>
  400545:       8b 45 fc                mov    -0x4(%rbp),%eax
  400548:       83 c0 01                add    $0x1,%eax
  40054b:       89 c7                   mov    %eax,%edi
  40054d:       e8 d4 ff ff ff          callq  400526 <loop>
  400552:       90                      nop
  400553:       c9                      leaveq
  400554:       c3                      retq

このファイルより編集箇所を特定します。
40054d番地のcallqをnop(C9)で埋めることにします。
実行ファイルをviエディタのバイナリーモードで開き、コマンド
:%!xxd
でバイナリーモードに
:%!xxd -r
で、元にもどします。
:w
で保存します。
バイナリーモードのときに、e8d4 d4ff などの文字列で検索をして場所を特定します。
recur_01
e8から5バイトをc9で埋めます。nopというのはNo Operationの略で何もしない、という意味です。サイズを変えられないバイナリファイルの編集時によく使います。
(条件分岐などを操作すると結構いろいろとできますが要注意)

recursion_e.dmp抜粋

0000000000400526 <loop>:
  400526:       55                      push   %rbp
  400527:       48 89 e5                mov    %rsp,%rbp
  40052a:       48 83 ec 10             sub    $0x10,%rsp
  40052e:       89 7d fc                mov    %edi,-0x4(%rbp)
  400531:       8b 45 fc                mov    -0x4(%rbp),%eax
  400534:       89 c6                   mov    %eax,%esi
  400536:       bf f4 05 40 00          mov    $0x4005f4,%edi
  40053b:       b8 00 00 00 00          mov    $0x0,%eax
  400540:       e8 bb fe ff ff          callq  400400 <printf@plt>
  400545:       8b 45 fc                mov    -0x4(%rbp),%eax
  400548:       83 c0 01                add    $0x1,%eax
  40054b:       89 c7                   mov    %eax,%edi
  40054d:       90                      nop
  40054e:       90                      nop
  40054f:       90                      nop
  400550:       90                      nop
  400551:       90                      nop
  400552:       90                      nop
  400553:       c9                      leaveq
  400554:       c3                      retq

バイナリが書き換えられているかどうか確認できました。
これを実行すると、

./recursion_e
1

loopが一回しか呼ばれないので、1のみ表示して終了します。

今回バイナリーの話題を取り上げるきっかけとなったのは、最近必要性が増していような気がしているためです。
以前も下記で取り上げましたが、
../../../20150913427/
FPGAやGPUプログラミングの必要性についても感じます。

コンピュータの進化がムーアの法則に従っているときはよかってのですが、そのような進化ができなくなり、かつ要求される処理の負荷が増大していることが起因しているからだと思います。
コンピュータリソースに余裕があるときは、人間にとってやさしい手法、生産性を高める手法が開発の現場でも用いられますが、今後は再びコンピュータリソースに余裕がなかった時代に戻る傾向にあるような気がします。(C言語は遅いからアセンブラで組め、などと言われた時代もあった)
難解な関数型言語が注目されるのもこの影響からだと思われます。
今回とりあげた末尾再帰についてもこの傾向によるところがあります。
マシン語のアドレスジャンプはBASICでいうGOTO文(C言語でもありますが)と同じで、構造化プログラミングでは使ってはいけないものとされていますが、再帰処理では欠かせないものになっています。

最近BASICから学ぶことが多い・・かも。。

]]>