Files
quartz-research-note/content/mimiumのMIRコンパイル過程を真面目に考える.md
松浦 知也 Matsuura Tomoya e7f0f2ccd2
Some checks failed
Build / build (push) Failing after 12m58s
[obsidian] vault backup: 2025-10-01 12:02:55[
2025-10-01 12:02:55 +09:00

4.0 KiB
Raw Blame History

date
date
2024-10-20 12:02

#mimium

ツリー形式からBasicBlockのインストラクション形式に変える

Stateのこととupvalueを両方処理しなくてはならない。ワンパスで処理できるのか

StateSize計算とUpvalue計算の両方をtraitとして切り出す方がいいのかな


\begin{align}
v \; ::= & \quad R \\
	| & (\lambda x:\tau.e, [\Gamma, x:e],StateStorage(p,Vec))  \quad & [Closure]\\
\end{align}

\begin{align}
e \; ::=& \quad x \quad x \in \mathbb{V} \quad & [value]\\
     |& \quad \lambda x.e  \quad & [lambda]\\
     |& \quad e \; e \quad & [appglobal,stateful)]\\
	 |& \quad appcls \; e \; e \quad & [appclosure]\\
	 |& \quad fix \; x.e  \quad & [fixpoint]\\
     |& \quad getstate \; e \; I_n \; I_s \quad & [feed] \\
|& \quad delay \; e \; e & [delay]\\
\end{align}

結局The w-calculus a synchronous framework for the verified modelling of digital signal processing algorithmsのStaged Interpreterと変わらんかもな

そうすると型付けの時点でクロージャ相当の項とグローバル関数適用の項は分かれることになる?エフェクトとして考えるのが妥当なのかな


\begin{align}
\tau ::=&\quad R_a \quad & a \in \mathbb{N}\\
      |&\quad I_n \quad &n \in \mathbb{N} \\\
      |&\quad \tau → \tau \quad \\
      % |&\quad \langle \tau \rangle
\end{align}

コンパイル

Valueとして、次のようなものがあり得る

struct VRegister(usize);//Vregisterは無限に使用できてサイズ可変
struct StackSlot(usize);
enum Value{
 Static(usize),
 Global(usize),
 Function(usize),
 ExternalItem(usize),
 Register(VRegister),
 StackSlot(StackSlot),//引数とかはこれで処理して良い
}

原則、Letが出てくればStackSlotに確保、そうでない一次変数はRegisterに確保でよい LetだけどRegisterに逃がせるとか、RegisterからStackへスピルするとかはオプティマイザの範疇 基本的にはFuncProtoはValueに依存しない構造にしたい

今はmir::ValueにmirgenとBytecodeGen両方が依存しているが、Valueは本来Expr→MIR生成時に使われるだけの中間的な値であるべき

struct MIR{
 name:Symbol,
 static_data:Vec<StaticData>
 externals:<ExternalItems>
 functions:Vec<FuncProto>
}

struct Operation{
	dest:Option<VRegister>,
	inst:Instruction
}
struct UpIndex(FnId,usize)
struct UpIndexStorage(Vec<StackSlot>)

impl UpIndexStorage{
 fn get_upv_index(&self,i:UpIndex)->Option<StackSlot>{
	 self.0.get(i.0)
 }
}
struct BlockId(usize)
struct BasicBlock{
 label:Symbol,
 entry_v:Vec<BlockId>
 op:Vec<Operation>
}
struct BlockChunk(Vec<Block>)
impl BlockChunk{ /*newtype pattern*/ }
struct FuncProto{
 name: Symbol,
 upindexes: UpIndexStorage,
 blocks: BlockChunk,
 state_tree: Vec<StateLeaf> //StateTreeは実際のデータを保持しない
 meta_labels:BTreeMap<StackSlot,Symbol> //print用メタデータ
}

enum Instruction{
  Load(StackSlot,Type)
  Store{dest:StackSlot,src:Register,Type},
  GetUpValue(UpIndex,Type),
  JmpIf{cond:Register,then:BlockId,else_:BlockId,merge:BlockId},
  ShiftStatePos(isize)//ツリーの子インデックスに対するカーソル移動オフセット
  GetState(Type)
  //その他、プリミティブな命令はfrom Register to Register
}

評価をしながら副作用としてMIRを書き込んでいくような感じ

fn eval(e:ExprNodeId,env:Env<(Value,Type)>)->(Value,Type){
	match e.to_expr() {
	 Expr::Id(name)=>{
		 match env.lookup(name){
		 (Value::Register(r),t)=>(r,t)
		 (Value::StackSlot(s),t)=>push_inst(Instruction::Load(s,t))
		 }
		 
	 },
	 Expr::Let(name,e,body)=>{
	     let env = env.extend((name,eval(e,env).0));
		 eval(e,env)
	 },
	 Expr::App(e,args)=>{
		 let (f,ft) = eval(e,env);
		 let argvs = args.iter().map(|e| eval(e,env));
		 match f{
			 Value::Closure(fproto,names,body,env)=>{
			 let kvs = names.iter().zip(argvs.zip()).collect()
			 let env = env.extend(kvs);
			  eval(body,env)
			 }
			 _=>panic!()
		 }
	 }
	 Expr::Feed(id,body)=>{
	   
	 }
	}

}