CSUS Programming Constest Control System

備忘録がてらICPC模擬地区大会及び地区大会で使用する"CSUS Programming Constest Control System"通称PC^2の使用法をまとめておこうと思います。Windows 7を前提に書いていますが、その他の環境でも同様の方法で動くと思います。以下、サーバー・ジャッジ・チームに分けて書いていきます。

サーバー・アドミン・ジャッジ・チーム共通

PC^2のインストール

公式サイトから適切なバージョンをダウンロードして適当なディレクトリに展開します。

アドミン・ジャッジ・チーム共通

PC^2の設定

pc2v9.iniにコンテストサーバーの情報を書き加えます。コンテストサーバーが"192.168.123.234"の場合、"client"セッションの下に"server=192.168.123.234"などと書けば良いと思います。

ジャッジ・チーム共通

コンパイラのインストール

本番でC++Javaを使うことを前提に書きます。

C++コンパイルにはg++を使うことになるのですが、cygwin版のg++は権限周りの問題で動かない場合があるようです。そこでMinGW版のg++を使用します。以前はMinGW版のg++のインストールはかなり煩雑だったように思うのですが、最近はmingw-get-instというインストーラーを使って楽をすることができるようです。mingw-get-instはsourceoforgeのダウンロードサイトからダウンロードできます。
インストーラーに従ってインストールするとC:\MinGWにインストールされます。オプションでg++にチェックを入れるのを忘れないようにしましょう。
続いてJDKをインストールします。JDKの最新版は公式サイトからダウンロードできます。

パスの設定

コントロールパネル->システム->システムの詳細設定->詳細設定->環境変数->**のユーザー環境変数->新規で、変数名"PATH"、変数値に"C:\MinGW\bin;C:\Program Files\Java\jdk1.6.0_**\bin"を追加します。ディレクトリ名はバージョンによって変わります。

サーバーのみ

pc2serverを起動します。起動する場合はpc9v2.iniの存在するディレクトリをカレントディレクトリとした後、相対パスを指定して起動します。"bin\pc2server"等となると思います。デスクトップにショートカットを作っておくと便利です。

Nameに"site1"、Passwordに"site1"を入力してLoginします。
次にコンテストパスワードを入力します。
コンテストパスワードは何でも良いようです。

アドミンのみ

pc2adminを起動します。起動方法はサーバーと同様です。Accounts・Languages・Problems・Timesを設定します。

アカウント

適切な数をgenerateすれば良いと思います。

Languages

Addボタンを押してGNU C++GNU C・Javaを追加します。
GNU C++GNU Cを追加する場合は、Compiler Command Lineに"-O2"を追加します。またExe NameとExecute Command Lineの末尾に".exe"を追加しておきます。
Javaを追加する場合はExecute Command Lineに最大ヒープサイズを設定するため"-Xmx256m"等を設定しておきます。

Problems

Addを押して問題を追加していきます。
問題タイトルには先頭に"Problem *: "を加えておいたほうが分かりやすいと思います。Run Time Limitは模擬地区大会では60秒程度を設定します。模擬地区大会ではファイル入力となりますので、Problem Requires Input Dataにチェック、Fileにチェックした後、入力データを指定します。Judge Have Provided Answer Fileにチェックした後、出力データを指定します。Show the output window・Show Compareもチェックを入れておきます。
Judging Typeはスタッフが余っている場合はManual Judgingで良いと思います。足りない場合はComputer Judgingが良いと思います。その際、設定の誤りによる不具合を防ぐためにManual Reviewをオンにしておいたほうが良いと思います。

Validatorでは判定方法を設定します。通常のdiffの場合はUse PC^2 Validatorで"1 - Diff"を選んでください。浮動小数validatorや独自Validatorが必要となる場合はUse External Validatorを選んでください。

ジャッジのみ

pc2judgeを起動してください。起動方法はserverと同様です。

チームのみ

pc2teamを起動してください。
つまらないミスを防ぐため、解答submitの前に必ずTestボタンでテストしましょう。*.inファイルをpc2と同じディレクトリに置いておくと、それを使ってコンパイル->実行までやってくれます。

外部リンク

オンラインジャッジ補助スクリプト

PKUとCodeforces用の補助スクリプトを過去に公開したのだが、一つのスクリプトで両方に対応したいと考えて統合してみた。さらに、M-JudgeとAOJにも対応してみた。

対応オンラインジャッジ

主な機能

  • サンプル入出力のダウンロード&テスト実行
  • テンプレートファイルのコピー
  • ソースコードのサブミット

対応言語

ダウンロード

以下のgithubからダウンロードできます

使用方法

overlastさんのブログにて詳しく解説されています。

2010/12/02 修正
2011/01/26 修正
  • C/C++/Javaに対応しました
  • CodeChefに対応しました
2011/02/13 追記

overlastさんのOverlasting::Lifeで本スクリプトの使用方法が紹介されました。詳細な使用方法感謝です。

2011/03/23 追記

Visual Studio 2010のヘルプを使いやすくするGreasemonkeyスクリプト

C++を書くときにはいつもVisual Studio 2010を使っている。Visual Studio 2010は個人的には非常によく出来たIDEだと思うのだが、非常につかづらい点が一つある。ヘルプだ。Visual Studio 2008の時のヘルプは専用のクライアントソフト(?)で閲覧する形になっており、キーワード検索の結果がリアルタイムで左下のウィンドウに表示されて使いやすかった。ところがVS2010でwebブラウザで表示する形式になって、リアルタイム検索がなくなってしまい、非常に使いづらくなってしまった。これをGreasemonkeyスクリプトで何とかできないかと思い、実際にやってみた。

やった内容は以下のとおり。

  • 検索キーワードが入力されたら、検索ウィンドウの下のスペースにその結果をリアルタイム表示
  • リアルタイム表示された結果がクリックされたらウィンドウ全体に表示

実際のスクリプトはコチラ

// ==UserScript==
// @name           VS2010 Incremental Search
// @namespace      http://kishibe.dyndns.tv/
// @description    VS2010 Incremental Search
// @include        http://127.0.0.1:47873/help/*
// ==/UserScript==
(function() {
	var url = window.self.location.href;
	if (window.top == window.self) {
		// メインウィンドウ
		if (url.indexOf("method=page") != -1 || url.indexOf("method=f1") != -1) {
			// ページ
			processMainWindowPage();
		}
	} else {
		// フレーム内
		if (url.indexOf("method=search") != -1) {
			// 検索結果
			processFrameSearch();
		} else if (url.indexOf("method=page") != -1
				|| url.indexOf("method=f1") != -1) {
			// ページ
			processFramePage();
		}
	}

	function processMainWindowPage() {
		var leftNavElement = document.getElementById("LeftNav");
		var iframeElement = document.createElement("iframe");
		iframeElement.width = "100%";
		iframeElement.height = "100%";
		iframeElement.scrolling = "no";
		leftNavElement.appendChild(iframeElement);

		update();
		setInterval(update, 1000);

		var lastSearchQuery = null;
		function update() {
			var searchTextBox = document.getElementById("qu");
			var searchQuery = searchTextBox.value;
			if (lastSearchQuery != searchQuery && searchQuery != "") {
				iframeElement.src = "ms.help?method=search&query="
						+ searchQuery
						+ "&btnS=&PageSize=10&PageNumber=1&locale=ja-JP&ProductVersion=100&Product=VS";
			}

			var tocElement = document.getElementById("toc");
			if (searchQuery == "") {
				tocElement.style.display = "block";
				iframeElement.style.display = "none";
			} else {
				tocElement.style.display = "none";
				iframeElement.style.display = "block";
			}

			lastSearchQuery = searchQuery;
		}
	}

	function processFrameSearch() {
		// タイトルとテキストボックスを隠す
		var divElements = document.getElementsByTagName("div");
		for ( var divElementIndex = 0; divElementIndex < divElements.length; ++divElementIndex) {
			var divElement = divElements[divElementIndex];
			if (divElement.className == "OH_topic") {
				divElement.parentNode.removeChild(divElement);
			}
		}

		var formElements = document.getElementsByTagName("form");
		var formElement = formElements[0];
		formElement.parentNode.removeChild(formElement);

		var pElements = document.getElementsByTagName("p");
		var pElement = pElements[0];
		pElement.parentNode.removeChild(pElement);
	}

	function processFramePage() {
		// 親ウィンドウに表示させる
		window.top.location = window.self.location;
	}
})();

これでVisual Studio 2008のヘルプの動作に近づき、少しだけ使いやすくなった。戻るボタンを押した時の動作が少々残念なことになっているのだが、それはおいおい考えることにする。

追記 (2010/11/08)

全文検索エンジンLuceneで自作のAnalyzer/Tokenizerを使用する

端的に言うと、AnalyzerとTokenizerを継承したクラスをそれぞれ作れば良い。

今回作成したTokenizerは以前よりQMACloneで使用している、辞書に含まれている単語を抜き出すというTokenizerである。アルゴリズムはVitabiアルゴリズムを少しだけ変えただけなので省略。ソースコード例はこちら↓

package tv.dyndns.kishibe.server.relevance;

import java.io.IOException;
import java.io.Reader;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TermAttribute;
import org.apache.lucene.util.AttributeSource;

public class VitabiTokenizer extends Tokenizer {
	private final TermAttribute termAtt;
	private final OffsetAttribute offsetAtt;
	private final List<Integer> offsets = new ArrayList<Integer>();
	private final List<Integer> lengths = new ArrayList<Integer>();
	private char[] buffer;
	private int wordIndex = 0;

	public VitabiTokenizer(Reader input) {
		super(input);
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(TermAttribute.class);
		initialize();
	}

	public VitabiTokenizer(AttributeFactory factory, Reader input) {
		super(factory, input);
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(TermAttribute.class);
		initialize();
	}

	public VitabiTokenizer(AttributeSource source, Reader input) {
		super(source, input);
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(TermAttribute.class);
		initialize();
	}

	private void initialize() {
		String s = null;
		try {
			s = IOUtils.toString(input);
		} catch (Exception e) {
			e.printStackTrace();
		}
		offsets.clear();
		lengths.clear();
		s = Normalizer.normalize(s, Form.NFKC);
		s = s.toLowerCase();
		Trie.getInstance().parse(s, null, offsets, lengths);
		buffer = s.toCharArray();
		wordIndex = 0;
	}

	@Override
	public boolean incrementToken() throws IOException {
		if (wordIndex == offsets.size()) {
			return false;
		} else {
			final int offset = offsets.get(wordIndex);
			final int length = lengths.get(wordIndex);
			++wordIndex;
			termAtt.setTermBuffer(buffer, offset, length);
			offsetAtt.setOffset(offset, offset + length);
			return true;
		}
	}

	@Override
	public void end() throws IOException {
		super.end();
		offsetAtt.setOffset(buffer.length, buffer.length);
	}

	@Override
	public void reset(Reader input) throws IOException {
		super.reset(input);
		initialize();
	}
}

Tokenizerを使用するクラスは、getAttribute()でTermAttributeとOffsetAttributeを取得して使用する。この準備のため、それぞれのインスタンスをフィールドに持たせておく。続いて実際のTokenizeを行う。本来はincrementToken()の中で1トークンずつ行うのだが、今回は既に用意してあったライブラリとの兼ね合いで、initialize()の中でTokenizeを行い、その結果を保持してincrementToken()でTokenizeの結果をTermAttributeとOffsetAttributeの中に入れるという形で行っている。もしかしたら速度が遅いかもしれない・・・。

Luceneを用いた全文検索

自分が開発・運営しているQMACloneでは全文検索エンジンtritonn-MySQLを使用している。tritonnを使用する場合、yumやaptでインストールすることができるMySQLパッケージを使うことが実質できなくなってしまう(正確には共存できるはずだが面倒)。このためメンテナンスや他のパッケージとの競合の解消が面倒になってしまう。

最近Twitter全文検索エンジンLuceneを採用したと聞き、自分も試してみることにした。

QMACloneにLuceneを組み込むにあたり、問題データ自体はMySQLに持たせ、ゲームサーバーの起動時に問題データをLuceneでインデックスに変換するという形にした。これは既存のソースコードの兼ね合いからである。書いたコードを備忘録を兼ねて掲載する。

インデックス化

まずは問題データをDocument化してIndexWriteでインデクスデータに変換する部分。

	private Document convertProblemDataToDocument(PacketProblemData problemData) {
		final Document document = new Document();

		// 問題番号
		final String problemId = "" + problemData.problemId;
		document.add(new Field(FIELD_PROBLEM_ID, problemId, Store.YES, Index.NO));

		// 問題文
		String sentence = problemData.sentence;
		sentence = Normalizer.normalize(sentence, Normalizer.Form.NFKC);
		document.add(new Field(FIELD_SENTENCE, sentence, Store.NO, Index.ANALYZED));

		// 問題文+選択肢+解答+問題ノート
		String searchQuery = problemData.getSearchQuery();
		searchQuery = Normalizer.normalize(searchQuery, Normalizer.Form.NFKC);
		document.add(new Field(FIELD_SEARCH, searchQuery, Store.NO, Index.ANALYZED));

		// 作問者
		String creator = problemData.creator;
		creator = Normalizer.normalize(creator, Normalizer.Form.NFKC);
		document.add(new Field(FIELD_CREATOR, creator, Store.NO, Index.ANALYZED));

		// ジャンル
		NumericField numericField = new NumericField(FIELD_GENRE, Store.NO, true);
		numericField.setIntValue(problemData.genre);
		document.add(numericField);

		// 出題形式
		numericField = new NumericField(FIELD_TYPE, Store.NO, true);
		numericField.setIntValue(problemData.type);
		document.add(numericField);

		// ランダム
		numericField = new NumericField(FIELD_RANDOM_FLAG, Store.NO, true);
		numericField.setIntValue(problemData.randomFlag);
		document.add(numericField);
		return document;
	}

Luceneは原則的には文字列しか扱うことができない。一方、元の問題データにはジャンル・出題形式・ランダムフラグは整数として持たせてある。これらの整数のデータをLuceneで扱うためにNumericFieldを使った。これとNumericRangeQueryを併用すると、Trie木を使って数値データを高速に検索できるようになるらしい。

続いてインデックスの作成部分。

	private class ProblemIndexWriter implements ProblemProcessor {
		private final IndexWriter indexWriter;

		public ProblemIndexWriter(IndexWriter indexWriter) {
			this.indexWriter = indexWriter;
		}

		@Override
		public void process(PacketProblemData problemData) throws Exception {
			final Document document = convertProblemDataToDocument(problemData);

			indexWriter.addDocument(document);
		}
	}

	public DirectDatabase() {
		delete(indexFileDirectory);
		indexFileDirectory.mkdirs();

		synchronized (lockIndexWriter) {
			IndexWriter writer = null;
			try {
				final FSDirectory d = FSDirectory.open(indexFileDirectory);
				final CJKAnalyzer analyzer = new CJKAnalyzer(Version.LUCENE_30);
				writer = new IndexWriter(d, analyzer, true, IndexWriter.MaxFieldLength.LIMITED);
				processProblems(new ProblemIndexWriter(writer));
				writer.optimize();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (writer != null) {
					try {
						writer.close();
					} catch (Exception e2) {
						e2.printStackTrace();
					}
					writer = null;
				}
			}
		}

		ThreadPool.getInstance().addDailyTask(runnableUpdateIndex);
	}

日本語の解析にはCJKAnalyzerを使った。これはbi-gramで文章を区切ってTermに変換するクラスだ。Lucene 3.0.*でCJKAnalyzerを使用する場合は「contrib/analyzers/common/lucene-analyzers-3.0.2.jar」をビルドパスに含める必要がある。

processProblems()はMySQLから問題データを取得して、引数で指定したハンドラに渡すメソッドである。ProblemIndexWriterは送られてきた問題データを上記のconvertProblemDataToDocument()でDocumentに変換してIndexWriterに渡すクラスである。これでゲームサーバーの起動時に問題データのインデックス化ができる。

問題データ更新

問題データが更新されたらインデックスも更新する。やり方はほとんど植えと同じなので割愛。更新の場合はaddDocument()の代わりにupdateDocument()が使える。

問題検索

続いて問題検索の部分。

	private static final String LUCENE_ESCAPE_CHARS = "(&&)|(\\|\\|)|[\\+\\-\\!\\(\\)\\{\\}\\[\\]\\^\\\"\\~\\*\\?\\:\\\\]";
	private static final Pattern LUCENE_PATTERN = Pattern.compile(LUCENE_ESCAPE_CHARS);
	private static final String REPLACEMENT_STRING = "\\\\$0";

	public static String escapeQuery(String t) {
		return LUCENE_PATTERN.matcher(t).replaceAll(REPLACEMENT_STRING);
	}

	private BooleanQuery concatenateQueries(List<Query> queries) {
		final BooleanQuery result = new BooleanQuery();
		for (Query query : queries) {
			result.add(query, Occur.MUST);
		}
		return result;
	}

	private List<Query> queryStringToLuceneQueries(String field, String queryString) throws ParseException {
		// 互換合成
		queryString = Normalizer.normalize(queryString, Form.NFKC);

		final CJKAnalyzer analyzer = new CJKAnalyzer(Version.LUCENE_30);
		final QueryParser parser = new QueryParser(Version.LUCENE_30, field, analyzer);

		final List<Query> queries = new ArrayList<Query>();
		final Scanner scanner = new Scanner(queryString);
		while (scanner.hasNext()) {
			final String t = scanner.next();
			final String escaped = escapeQuery(t);
			final Query q = parser.parse(escaped);
			queries.add(q);
		}
		return queries;
	}

	private Query bitFlagToQuery(String field, int bitFlag) {
		final BooleanQuery query = new BooleanQuery();
		int begin = -1;
		for (int i = 0; i < 31; ++i) {
			if ((bitFlag & (1 << i)) == 0) {
				if (begin == -1) {
					continue;
				}
				final Query q = NumericRangeQuery.newIntRange(field, begin, i, true, false);
				query.add(q, Occur.SHOULD);
				begin = -1;
			} else {
				if (begin == -1) {
					begin = i;
				}
			}
		}

		return query;
	}

	public PacketProblemData[] searchProblem(final String queryString, final String creater, int genre, int type, int randomFlag) {
		final boolean queryEmpty = (queryString == null || queryString.isEmpty());
		final boolean createrEmpty = (creater == null || creater.isEmpty());
		if (queryEmpty && createrEmpty && genre <= 1 && type <= 1) {
			return new PacketProblemData[0];
		}

		if (genre == 0 || (genre & 1) == 1) {
			genre = (1 << Constant.NUMBER_OF_GENRE) - 1;
		}
		if (type == 0 || (type & 1) == 1) {
			type = (1 << Constant.NUMBER_OF_TYPE) - 1;
		}
		if (randomFlag == 0 || (randomFlag & 1) == 1) {
			randomFlag = (1 << Constant.NUMBER_OF_RANDOM) - 1;
		}

		final BooleanQuery query = new BooleanQuery();

		// 問題文
		if (!queryEmpty) {
			try {
				for (Query q : queryStringToLuceneQueries(FIELD_SEARCH, queryString)) {
					query.add(q, Occur.MUST);
				}
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		// 作成者
		if (!createrEmpty) {
			try {
				for (Query q : queryStringToLuceneQueries(FIELD_CREATOR, creater)) {
					query.add(q, Occur.MUST);
				}
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		// ジャンル
		query.add(bitFlagToQuery(FIELD_GENRE, genre), Occur.MUST);

		// 出題形式
		query.add(bitFlagToQuery(FIELD_TYPE, type), Occur.MUST);

		// ランダムフラグ
		query.add(bitFlagToQuery(FIELD_RANDOM_FLAG, randomFlag), Occur.MUST);

		IndexReader reader = null;
		try {
			reader = IndexReader.open(FSDirectory.open(indexFileDirectory), true);

			final Searcher searcher = new IndexSearcher(reader);

			final TopDocs docs = searcher.search(query, MAX_NUMBER_OF_SEARCH_REUSLTS);
			final List<Integer> problemIds = new ArrayList<Integer>();
			for (ScoreDoc doc : docs.scoreDocs) {
				final Document document = reader.document(doc.doc);
				final int problemId = Integer.parseInt(document.get(FIELD_PROBLEM_ID));
				problemIds.add(problemId);
			}

			if (problemIds.isEmpty()) {
				return new PacketProblemData[0];
			}
			return getProblemData(problemIds);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				reader.clone();
				reader = null;
			}
		}

		return null;
	}

本当はTermQueryが使いたかったのだが、何らかの原因で正しく検索ができなかった。おそらく文字のノーマライズ処理のあたりが原因だと思う。このためCJKAnalyzerとQueryParserを使うことにした。

LuceneではQuery Parserに渡すクエリに「+ - && || ! ( ) { } [ ] ^ " ~ * ? : \」が含まれている場合、先頭に「\」を付けてエスケープしなければならない。これらのエスケープ処理をescapeQuery()で行っている。

bitFlagToQuery()ではビットフラグをNumericRangeQueryに変換している。複数のジャンル・問題形式・ランダムフラグが指定されたときは、この部分で絞り込みを行う。

類似問題検索

類似問題の検索部分。QMACloneでは独自実装した関連文書検索エンジンを使用している。これはWikipediaのタイトル一覧とニコニコ大百科の記事タイトル一覧を単語辞書と見立てて、tf-idfを元に類似文書を見つけるという物である。少し前に流行ったやり方だと思う。この自家製関連文書検索エンジンは極稀に検索に失敗する場合がある。失敗した場合はMySQL全文検索エンジンを使って検索し直すようにしていた。この失敗した場合の検索ルーチンをLuceneに置き換えてみた。

	public PacketProblemData[] searchSimilarProblemDataFromDatabase(PacketProblemData problemData) {
		IndexReader reader = null;
		try {
			reader = IndexReader.open(FSDirectory.open(indexFileDirectory), true);

			final Searcher searcher = new IndexSearcher(reader);

			final String[] fields = new String[] { FIELD_SEARCH };
			final Analyzer analyzer = new CJKAnalyzer(Version.LUCENE_30);
			final MoreLikeThisQuery query = new MoreLikeThisQuery(problemData.getSearchQuery(), fields, analyzer);

			final TopDocs docs = searcher.search(query, 10);
			final List<Integer> problemIds = new ArrayList<Integer>();
			for (ScoreDoc doc : docs.scoreDocs) {
				final Document document = reader.document(doc.doc);
				final int problemId = Integer.parseInt(document.get(FIELD_PROBLEM_ID));
				problemIds.add(problemId);
			}

			if (problemIds.isEmpty()) {
				return new PacketProblemData[0];
			}
			return getProblemData(problemIds);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				reader.clone();
				reader = null;
			}
		}

		return null;
	}

MoreLikeThisQueryは類似文書の検索クエリである。他のクエリと同様に扱うことができるので便利だと思う。使用する場合は「contrib/queries/lucene-queries-3.0.2.jar」をビルドパスに含める必要がある。

ちなみに自家製検索エンジンとMoreLikeThisQueryで検索した場合の違いはこんな感じ。クエリは"「Google」で2020年完成予定の 人工知能で会話しつつ検索などを行うサービスを 「Google ○○○○○」という? BRAIN"。まずは自家製検索エンジン

173047 - 5 - 9 - サーバのサービスをすべて選びなさい - DNS - SMTP - DHCP -  - DNS - SMTP - DHCP - VTEC - 
174780 - 1 - 4 - 2010年よりサービスを開始するコナミの電子マネーシステムは? - PASELI -  -  -  -  -  -  -  - 
174781 - 1 - 7 - 2010年よりサービスを開始するコナミの電子マネーシステムです。 - PASELI -  -  -  -  -  -  -  - 
128595 - 2 - 6 - テニスにおいてサーバーがサービスゲームを取得することを○○○という?%n○を答えなさい - キープ -  -  -  -  -  -  -  - 
35275 - 4 - 6 - Googleの検索バーに%n「人生、宇宙、すべての答え」と打ち込むと%n電卓機能が示す答えはいくつ?%n数字で答えなさい。 - 42 -  -  -  -  -  -  -  - 
163987 - 3 - 7 - Google社が開発・配布している、%n写真を検索したり共有するためのソフトウェアです - PICASA -  -  -  -  -  -  -  - 
56017 - 4 - 11 - 次の古語とその意味の%n正しい組み合わせを答えなさい - 会話をする - 歳をとる - 遠ざかる - 塞ぎ込む - こととふ - ねぶ - かる - くんず - 
185084 - 4 - 7 - 回答例を元に質問者の思い浮かべた%n人物やキャラクターを当てる%nインターネット上で話題となった人工知能の名前 - アキネーター -  -  -  -  -  -  -  - 
68562 - 4 - 7 - 『Google』や『Yahoo』等が有名な%n検索エンジンを2つに分けると%n『ロボット型』と『?型』 - ディレクトリ -  -  -  -  -  -  -  - 
32137 - 4 - 6 - 「Google」で2020年完成予定の%n人工知能で会話しつつ検索などを行うサービスを%n「Google ○○○○○」という? - BRAIN -  -  -  -  -  -  -  - 

Google」「サービス」という単語に引っかかっているらしい。続いてMoreLikeThisQuery。

32137 - 4 - 6 - 「Google」で2020年完成予定の%n人工知能で会話しつつ検索などを行うサービスを%n「Google ○○○○○」という? - BRAIN -  -  -  -  -  -  -  - 
182705 - 4 - 8 - 2011年完成予定の東京スカイツリーの%n最寄り駅です。 - なりひらばし -  -  -  - 業平橋 -  -  -  - 
158864 - 4 - 12 - 香港一高い建物になる2010年完成予定のこの超高層ビルは? - 環球貿易広場 - 国際金融中心 - 西九龍電波塔 - 香港上海銀行 -  -  -  -  - 
158237 - 4 - 9 - 次のうち%nGoogleが提供するサービスに%n実際にあるものを全て選びなさい - Google Earth - Google Sky - Google Moon - Google Mars - Google Earth - Google Sky - Google Moon - Google Mars - 
62940 - 4 - 9 - 次のうち、2007年6月現在、検索エンジン「Google」にアクセスできるアドレスを全て選びなさい。 - http://www.google.tv/ - http://www.google.info/ - http://www.google.kg/ - http://www.google.cc/ - http://www.google.tv/ - http://www.google.info/ - http://www.google.kg/ - http://www.google.cc/ - 
80279 - 4 - 10 - 次のGoogleのサービスを開始されたのが早い順に選びなさい。 - Gmail - Google Video - Google Earth - Chrome -  -  -  -  - 
30862 - 3 - 3 - solitude%w%nEternally Beyond%w%nDialogue Symphonie%w%w%nLa dix croix - Moi dix Mois -  -  -  - Moi dix Mois - Sulfulic Acid - Brain Hacker - MALICE MIZER - 
84336 - 4 - 4 - 脳・神経に関係する英単語で、「脳」といえば? - brain -  -  -  -  -  -  -  - 
12858 - 1 - 2 - 次のうち、%nアニメ『攻殻機動隊』シリーズの%n舞台となる年代は? - 西暦2030年代 -  -  -  - 西暦2030年代 - 西暦2020年代 - 西暦2040年代 - 西暦2050年代 - 
128401 - 5 - 7 - 北海道夕張市の大夕張ダムの下流に%n現在建設中のダムは「夕張○○○○○ダム」? - シューパロ -  -  -  -  -  -  -  - 

「完成予定」「Google」「2020年」という単語に引っかかっているらしい。

LLVMでJITアドレス空間にある関数に別の関数へのポインタを渡して呼び出す

LLVMで(ry

ものすごくどうでも良いようなことをやっているような気がしないでもないのだが、とりあえずできたので書く。

前回二つ分の合わせ技で行けるらしい。LLVMには関数ポインタを表す型は用意されていないっぽい(?)ので、代わりにi8*を使用する。ソースコードは以下の通り。

void printInteger(unsigned int i) {
	printf("printInteger() : %d\n", i);
}

extern "C"
__declspec(dllexport) void callPrintInteger(void (*function)(unsigned int), unsigned int i) {
	function(i);
}

int main() {
	InitializeNativeTarget();

	LLVMContext Context;

	Module *M = new Module("test", Context);

	Function *testFunction = cast<Function>(M->getOrInsertFunction("testFunction", Type::getVoidTy(Context), (Type *)0));
	BasicBlock *BB = BasicBlock::Create(Context, "EntryBlock", testFunction);
	IRBuilder<> builder(BB);

	Value *One = ConstantInt::get(Type::getInt32Ty(Context), 1);

	std::vector<const Type*> Params;
	Params.push_back(Type::getInt8PtrTy(Context));
	Params.push_back(Type::getInt32Ty(Context));
	FunctionType *FT = FunctionType::get(Type::getVoidTy(Context), Params, false);
	Function *F = Function::Create(FT, Function::ExternalLinkage, "callPrintInteger", M);
	Constant* integer = ConstantInt::get(Type::getInt64Ty(Context), (unsigned long long)&printInteger);
	Value* pointer = ConstantExpr::getIntToPtr(integer, PointerType::getUnqual(Type::getInt8Ty(Context)));
	builder.CreateCall2(M->getFunction("callPrintInteger"), pointer, One);

	builder.CreateRetVoid();

	ExecutionEngine* EE = EngineBuilder(M).create();

	std::vector<GenericValue> noargs;
	EE->runFunction(testFunction, noargs);

	outs() << "Module:\n" << *M;

	EE->freeMachineCodeForFunction(testFunction);
	delete EE;
	llvm_shutdown();
	return 0;
}

一度キャストしているところがどうにも気持ち悪い。どうにかならないものだろうか・・・。

LLVMでJITアドレス空間にある関数を使用する

前の記事に引き続いてLLVMJITアドレス空間にある関数にアクセスする方法。

LLVMチュートリアルのCh.4にやり方が載っているのだが、そのままでは動かなかった。理由はLLVMが関数のシンボルを見つけられないかららしい。VisualStudioではシンボルのエクスポートをするおまじない(?)を書く必要がある。Linuxでは"-rdynamic"コンパイルオプション付きでコンパイルすればよいらしいが、試していないのでよく分からない。ソースコードは以下の通り。

extern "C"
__declspec(dllexport) void printHelloWorld() {
	printf("Hello, World!\n");
}

int main() {
	InitializeNativeTarget();

	LLVMContext Context;

	Module *M = new Module("test", Context);

	Function *testFunction = cast<Function>(M->getOrInsertFunction("testFunction", Type::getVoidTy(Context), (Type *)0));
	BasicBlock *BB = BasicBlock::Create(Context, "EntryBlock", testFunction);
	IRBuilder<> builder(BB);

	FunctionType *FT = FunctionType::get(Type::getVoidTy(Context), false);
	Function *F = Function::Create(FT, Function::ExternalLinkage, "printHelloWorld", M);
	builder.CreateCall(M->getFunction("printHelloWorld"));

	builder.CreateRetVoid();

	ExecutionEngine* EE = EngineBuilder(M).create();

	std::vector<GenericValue> noargs;
	EE->runFunction(testFunction, noargs);

	EE->freeMachineCodeForFunction(testFunction);
	delete EE;
	llvm_shutdown();
	return 0;
}