Files
hakorune/docs/development/roadmap/native-plan/llvm/issue/001-setup-inkwell-hello-world.md

7.6 KiB
Raw Blame History

🚀 Issue #001: LLVM PoC - inkwellセットアップとHello World実装

タイプ: Feature
優先度: Critical
見積もり: 3日
担当: Copilot

📋 概要

Phase 9.78 LLVM PoCの第一歩として、inkwellクレートを導入し、最小限のNyashプログラムreturn 42をLLVM経由で実行できるようにする。

🎯 成功条件

以下のNyashプログラムがLLVM経由で実行され、正しい終了コードを返すこと

// test_return_42.hako
static box Main {
    main() {
        return 42
    }
}

期待される動作:

$ cargo run --features llvm -- --backend llvm test_return_42.hako
$ echo $?
42

📝 実装タスク

1. Cargo.toml更新 必須

[dependencies]
inkwell = { version = "0.5", features = ["llvm17-0"] }

[features]
llvm = ["inkwell"]

2. 基本構造の作成 必須

// src/backend/llvm/mod.rs
pub mod context;
pub mod compiler;

use crate::mir::module::MirModule;
use crate::errors::RuntimeError;

pub fn compile_to_object(
    mir_module: &MirModule,
    output_path: &str,
) -> Result<(), RuntimeError> {
    let compiler = compiler::LLVMCompiler::new()?;
    compiler.compile_module(mir_module, output_path)
}

3. LLVMコンテキスト管理 必須

// src/backend/llvm/context.rs
use inkwell::context::Context;
use inkwell::module::Module;
use inkwell::builder::Builder;
use inkwell::targets::{Target, TargetMachine, TargetTriple, InitializationConfig};

pub struct CodegenContext<'ctx> {
    pub context: &'ctx Context,
    pub module: Module<'ctx>,
    pub builder: Builder<'ctx>,
    pub target_machine: TargetMachine,
}

impl<'ctx> CodegenContext<'ctx> {
    pub fn new(context: &'ctx Context, module_name: &str) -> Result<Self, String> {
        // 1. ターゲット初期化
        Target::initialize_native(&InitializationConfig::default())
            .map_err(|e| format!("Failed to initialize native target: {}", e))?;
        
        // 2. モジュール作成
        let module = context.create_module(module_name);
        
        // 3. ターゲットマシン作成
        let triple = TargetMachine::get_default_triple();
        let target = Target::from_triple(&triple)
            .map_err(|e| format!("Failed to get target: {}", e))?;
        let target_machine = target
            .create_target_machine(
                &triple,
                "generic",
                "",
                inkwell::OptimizationLevel::None,
                inkwell::targets::RelocMode::Default,
                inkwell::targets::CodeModel::Default,
            )
            .ok_or_else(|| "Failed to create target machine".to_string())?;
        
        // 4. データレイアウト設定
        module.set_triple(&triple);
        module.set_data_layout(&target_machine.get_target_data().get_data_layout());
        
        Ok(Self {
            context,
            module,
            builder: context.create_builder(),
            target_machine,
        })
    }
}

4. 最小限のコンパイラ実装 必須

// src/backend/llvm/compiler.rs
use inkwell::context::Context;
use inkwell::values::IntValue;
use crate::mir::module::MirModule;
use crate::mir::instruction::MirInstruction;
use super::context::CodegenContext;

pub struct LLVMCompiler {
    context: Context,
}

impl LLVMCompiler {
    pub fn new() -> Result<Self, String> {
        Ok(Self {
            context: Context::create(),
        })
    }
    
    pub fn compile_module(
        &self,
        mir_module: &MirModule,
        output_path: &str,
    ) -> Result<(), String> {
        let codegen = CodegenContext::new(&self.context, "nyash_module")?;
        
        // 1. main関数を探す
        let main_func = mir_module.functions.iter()
            .find(|f| f.name == "Main.main")
            .ok_or("Main.main function not found")?;
        
        // 2. LLVM関数を作成
        let i32_type = codegen.context.i32_type();
        let fn_type = i32_type.fn_type(&[], false);
        let llvm_func = codegen.module.add_function("main", fn_type, None);
        
        // 3. エントリブロックを作成
        let entry = codegen.context.append_basic_block(llvm_func, "entry");
        codegen.builder.position_at_end(entry);
        
        // 4. MIR命令を処理今回はReturnのみ
        for block in &main_func.blocks {
            for inst in &block.instructions {
                match inst {
                    MirInstruction::Return(Some(value_id)) => {
                        // 簡易実装: 定数42を返すと仮定
                        let ret_val = i32_type.const_int(42, false);
                        codegen.builder.build_return(Some(&ret_val));
                    }
                    _ => {
                        // 他の命令は今回スキップ
                    }
                }
            }
        }
        
        // 5. 検証
        if !llvm_func.verify(true) {
            return Err("Function verification failed".to_string());
        }
        
        // 6. オブジェクトファイル生成
        codegen.target_machine
            .write_to_file(&codegen.module, 
                         inkwell::targets::FileType::Object, 
                         output_path.as_ref())
            .map_err(|e| format!("Failed to write object file: {}", e))?;
        
        Ok(())
    }
}

5. バックエンド統合 必須

// src/backend/mod.rsに追加
#[cfg(feature = "llvm")]
pub mod llvm;

// src/runner.rsのrun_with_backend関数に追加
#[cfg(feature = "llvm")]
ExecutionBackend::LLVM => {
    // 1. オブジェクトファイル生成
    let obj_path = "nyash_output.o";
    crate::backend::llvm::compile_to_object(&mir_module, obj_path)?;
    
    // 2. リンク簡易版システムのccを使用
    use std::process::Command;
    let output = Command::new("cc")
        .args(&[obj_path, "-o", "nyash_output"])
        .output()
        .map_err(|e| RuntimeError::new(format!("Link failed: {}", e)))?;
    
    if !output.status.success() {
        return Err(RuntimeError::new("Linking failed"));
    }
    
    // 3. 実行
    let output = Command::new("./nyash_output")
        .output()
        .map_err(|e| RuntimeError::new(format!("Execution failed: {}", e)))?;
    
    // 4. 終了コードを返す
    let exit_code = output.status.code().unwrap_or(-1);
    Ok(Box::new(IntegerBox::new(exit_code as i64)))
}

🧪 テストケース

// tests/llvm_hello_world.rs
#[test]
#[cfg(feature = "llvm")]
fn test_return_42() {
    let source = r#"
        static box Main {
            main() {
                return 42
            }
        }
    "#;
    
    // パース → MIR生成 → LLVM実行
    let result = compile_and_run_llvm(source);
    assert_eq!(result, 42);
}

📚 参考資料

⚠️ 注意事項

  1. LLVM依存関係: LLVM 17がシステムにインストールされている必要があります
  2. プラットフォーム: まずはLinux/macOSで動作確認し、Windowsは後回し
  3. エラーハンドリング: 今回は最小実装のため、詳細なエラー処理は省略

🎯 次のステップ

このIssueが完了したら、次は

  • Issue #002: 基本的な算術演算の実装BinOp
  • Issue #003: 定数値の実装Const

作成者: Claude + moe-charm
レビュアー: AIチーム
関連PR: (作成予定)