mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-28 08:44:06 +02:00
compiler: add lir
This commit is contained in:
parent
432053bcaf
commit
8a8dce419e
@ -1,10 +1,10 @@
|
|||||||
import * as path from "jsr:@std/path";
|
import * as path from "jsr:@std/path";
|
||||||
import { Parser } from "./parse/parser.ts";
|
import { Parser } from "./parse/parser.ts";
|
||||||
import * as ast from "./ast/mod.ts";
|
import * as ast from "@slige/ast";
|
||||||
import { Ctx, File } from "@slige/common";
|
import { Ctx, File } from "@slige/common";
|
||||||
import { Resolver } from "./resolve/resolver.ts";
|
import { Resolver } from "./resolve/resolver.ts";
|
||||||
import { Checker } from "./check/checker.ts";
|
import { Checker } from "./check/checker.ts";
|
||||||
import { AstLowerer } from "./middle/ast_lower.ts";
|
import { ast_lower, mir_lower } from "@slige/middle";
|
||||||
import { HirStringifyer } from "@slige/stringify";
|
import { HirStringifyer } from "@slige/stringify";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@ -53,7 +53,7 @@ export class PackCompiler {
|
|||||||
console.error("error(s) occurred.");
|
console.error("error(s) occurred.");
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
const astLowerer = new AstLowerer(
|
const astLowerer = new ast_lower.AstLowerer(
|
||||||
this.ctx,
|
this.ctx,
|
||||||
resols,
|
resols,
|
||||||
checker,
|
checker,
|
||||||
@ -65,6 +65,16 @@ export class PackCompiler {
|
|||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
console.log("=== MIR ===\n" + astLowerer.mirString());
|
console.log("=== MIR ===\n" + astLowerer.mirString());
|
||||||
|
if (this.ctx.errorOccured()) {
|
||||||
|
console.error("error(s) occurred. stopping...");
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
const mirLowerer = new mir_lower.MirLowerer(
|
||||||
|
this.ctx,
|
||||||
|
astLowerer.toArray(),
|
||||||
|
);
|
||||||
|
mirLowerer.lower();
|
||||||
|
console.log("=== LIR ===\n" + mirLowerer.lirString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public enableDebug() {
|
public enableDebug() {
|
||||||
|
@ -35,6 +35,10 @@ export class AstLowerer implements ast.Visitor {
|
|||||||
.toArray()
|
.toArray()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toArray(): Fn[] {
|
||||||
|
return this.fns.values().toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FnLowerer {
|
export class FnLowerer {
|
||||||
|
62
slige/compiler/middle/lir.ts
Normal file
62
slige/compiler/middle/lir.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { IdBase, IdMap } from "@slige/common";
|
||||||
|
import * as mir from "./mir.ts";
|
||||||
|
import { BlockId } from "./mir.ts";
|
||||||
|
|
||||||
|
export type Fn = {
|
||||||
|
mirFn: mir.Fn;
|
||||||
|
blocks: IdMap<BlockId, Block>;
|
||||||
|
locals: IdMap<LocalId, Local>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LocalId = IdBase & { readonly _: unique symbol };
|
||||||
|
|
||||||
|
export type Local = {
|
||||||
|
id: LocalId;
|
||||||
|
base: mir.LocalId;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Block = {
|
||||||
|
id: mir.BlockId;
|
||||||
|
stmts: Stmt[];
|
||||||
|
ter: Ter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Stmt = {
|
||||||
|
kind: StmtKind;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StmtKind =
|
||||||
|
| { tag: "error" }
|
||||||
|
| { tag: "assign"; local: LocalId; rval: RVal };
|
||||||
|
|
||||||
|
export type Ter = {
|
||||||
|
kind: TerKind;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TerKind =
|
||||||
|
| { tag: "error" }
|
||||||
|
| { tag: "goto"; target: BlockId }
|
||||||
|
| {
|
||||||
|
tag: "switch";
|
||||||
|
discr: RVal;
|
||||||
|
targets: SwitchTarget[];
|
||||||
|
otherwise: BlockId;
|
||||||
|
}
|
||||||
|
| { tag: "return" };
|
||||||
|
|
||||||
|
export type SwitchTarget = {
|
||||||
|
value: number;
|
||||||
|
target: BlockId;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PhiSource = {
|
||||||
|
local: LocalId;
|
||||||
|
branch: BlockId;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RVal =
|
||||||
|
| { tag: "error" }
|
||||||
|
| { tag: "phi"; sources: PhiSource[] }
|
||||||
|
| { tag: "use"; local: LocalId }
|
||||||
|
| { tag: "const"; val: mir.Const };
|
@ -35,13 +35,15 @@ export type Stmt = {
|
|||||||
|
|
||||||
export type StmtKind =
|
export type StmtKind =
|
||||||
| { tag: "error" }
|
| { tag: "error" }
|
||||||
| { tag: "assign"; place: Place; rval: RVal }
|
| { tag: "assign" } & AssignStmt
|
||||||
| { tag: "fake_read"; place: Place }
|
| { tag: "fake_read"; place: Place }
|
||||||
| { tag: "deinit"; place: Place }
|
| { tag: "deinit"; place: Place }
|
||||||
| { tag: "live"; local: LocalId }
|
| { tag: "live"; local: LocalId }
|
||||||
| { tag: "dead"; local: LocalId }
|
| { tag: "dead"; local: LocalId }
|
||||||
| { tag: "mention"; place: Place };
|
| { tag: "mention"; place: Place };
|
||||||
|
|
||||||
|
export type AssignStmt = { place: Place; rval: RVal };
|
||||||
|
|
||||||
export type Ter = {
|
export type Ter = {
|
||||||
kind: TerKind;
|
kind: TerKind;
|
||||||
};
|
};
|
||||||
|
259
slige/compiler/middle/mir_lower.ts
Normal file
259
slige/compiler/middle/mir_lower.ts
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import * as mir from "./mir.ts";
|
||||||
|
import * as lir from "./lir.ts";
|
||||||
|
import { Ctx, exhausted, IdMap, Ids, todo } from "@slige/common";
|
||||||
|
import { BlockId } from "./mir.ts";
|
||||||
|
import { LirFnStringifyer } from "@slige/stringify";
|
||||||
|
|
||||||
|
export class MirLowerer {
|
||||||
|
private lirFns: lir.Fn[] = [];
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private ctx: Ctx,
|
||||||
|
private mirFns: mir.Fn[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public lower() {
|
||||||
|
for (const fn of this.mirFns) {
|
||||||
|
this.lirFns.push(new FnLowerer(fn).lower());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public lirString(): string {
|
||||||
|
return this.lirFns
|
||||||
|
.values()
|
||||||
|
.map((fn) => new LirFnStringifyer(this.ctx).fn(fn))
|
||||||
|
.toArray()
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FnLowerer {
|
||||||
|
private localIds = new Ids<lir.LocalId>();
|
||||||
|
private locals = new IdMap<lir.LocalId, lir.Local>();
|
||||||
|
|
||||||
|
private localVersionCounter = new IdMap<mir.LocalId, number>();
|
||||||
|
|
||||||
|
private blocks = new IdMap<BlockId, lir.Block>();
|
||||||
|
|
||||||
|
private blockLocals = new IdMap<BlockId, IdMap<mir.LocalId, lir.Local>>();
|
||||||
|
|
||||||
|
private currentBlockId?: BlockId;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private fn: mir.Fn,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public lower(): lir.Fn {
|
||||||
|
for (const mirBlock of this.fn.blocks.values()) {
|
||||||
|
const lirBlock = this.lowerBlock(mirBlock);
|
||||||
|
this.blocks.set(lirBlock.id, lirBlock);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mirFn: this.fn,
|
||||||
|
blocks: this.blocks,
|
||||||
|
locals: this.locals,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private lowerBlock(block: mir.Block): lir.Block {
|
||||||
|
this.currentBlockId = block.id;
|
||||||
|
this.blockLocals.set(block.id, new IdMap());
|
||||||
|
const stmts: lir.Stmt[] = [];
|
||||||
|
|
||||||
|
const locals = new IdMap<mir.LocalId, lir.Local>();
|
||||||
|
for (const mirId of this.fn.locals.keys()) {
|
||||||
|
const lirId = this.localIds.nextThenStep();
|
||||||
|
this.localVersionCounter.set(mirId, 0);
|
||||||
|
const local: lir.Local = {
|
||||||
|
id: lirId,
|
||||||
|
base: mirId,
|
||||||
|
version: block.id.rawId,
|
||||||
|
};
|
||||||
|
locals.set(mirId, local);
|
||||||
|
this.locals.set(lirId, local);
|
||||||
|
this.blockLocals.get(block.id)!.set(mirId, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
const superBlocks = this.fn.blocks
|
||||||
|
.values()
|
||||||
|
.filter((b) => blockHasTarget(b, block.id))
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
if (block.id.rawId !== 0) {
|
||||||
|
for (const mirId of locals.keys()) {
|
||||||
|
const sources: lir.PhiSource[] = [];
|
||||||
|
for (const superBlock of superBlocks) {
|
||||||
|
const local = this.blockLocals.get(superBlock.id)!.get(
|
||||||
|
mirId,
|
||||||
|
)!;
|
||||||
|
sources.push({ branch: superBlock.id, local: local.id });
|
||||||
|
}
|
||||||
|
stmts.push(
|
||||||
|
this.stmt({
|
||||||
|
tag: "assign",
|
||||||
|
local: locals.get(mirId)!.id,
|
||||||
|
rval: { tag: "phi", sources },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const stmt of block.stmts) {
|
||||||
|
stmts.push(...this.lowerStmt(stmt));
|
||||||
|
}
|
||||||
|
const [s1, ter] = this.lowerTer(block.terminator);
|
||||||
|
stmts.push(...s1);
|
||||||
|
return { id: block.id, stmts, ter };
|
||||||
|
}
|
||||||
|
|
||||||
|
private lowerStmt(stmt: mir.Stmt): lir.Stmt[] {
|
||||||
|
const k = stmt.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "error":
|
||||||
|
return [this.stmt({ tag: "error" })];
|
||||||
|
case "assign":
|
||||||
|
return this.lowerAssignStmt(stmt, k);
|
||||||
|
case "fake_read":
|
||||||
|
return [this.stmt({ tag: "error" })];
|
||||||
|
case "deinit":
|
||||||
|
return todo();
|
||||||
|
case "live":
|
||||||
|
return todo();
|
||||||
|
case "dead":
|
||||||
|
return todo();
|
||||||
|
case "mention":
|
||||||
|
return todo();
|
||||||
|
}
|
||||||
|
exhausted(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
private lowerAssignStmt(stmt: mir.Stmt, kind: mir.AssignStmt): lir.Stmt[] {
|
||||||
|
if (kind.place.proj.length !== 0) {
|
||||||
|
return todo();
|
||||||
|
}
|
||||||
|
const [s1, rval] = this.lowerRVal(kind.rval);
|
||||||
|
|
||||||
|
const version = this.localVersionCounter.get(kind.place.local)!;
|
||||||
|
this.localVersionCounter.set(kind.place.local, version + 1);
|
||||||
|
|
||||||
|
const lirId = this.localIds.nextThenStep();
|
||||||
|
this.blockLocals
|
||||||
|
.get(this.currentBlockId!)!
|
||||||
|
.set(kind.place.local, {
|
||||||
|
id: lirId,
|
||||||
|
base: kind.place.local,
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...s1, this.stmt({ tag: "assign", local: lirId, rval })];
|
||||||
|
}
|
||||||
|
|
||||||
|
private lowerRVal(rval: mir.RVal): [lir.Stmt[], lir.RVal] {
|
||||||
|
switch (rval.tag) {
|
||||||
|
case "error":
|
||||||
|
return [[], { tag: "error" }];
|
||||||
|
case "use": {
|
||||||
|
switch (rval.operand.tag) {
|
||||||
|
case "error":
|
||||||
|
return [[], { tag: "error" }];
|
||||||
|
case "copy":
|
||||||
|
case "move":
|
||||||
|
return [[], {
|
||||||
|
tag: "use",
|
||||||
|
local: this.blockLocals
|
||||||
|
.get(this.currentBlockId!)!
|
||||||
|
.get(rval.operand.place.local)!.id,
|
||||||
|
}];
|
||||||
|
case "const":
|
||||||
|
return [[], {
|
||||||
|
tag: "const",
|
||||||
|
val: rval.operand.val,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return exhausted(rval.operand);
|
||||||
|
}
|
||||||
|
case "repeat":
|
||||||
|
case "ref":
|
||||||
|
case "ptr":
|
||||||
|
case "binary":
|
||||||
|
case "unary":
|
||||||
|
return todo();
|
||||||
|
case "adt":
|
||||||
|
case "call":
|
||||||
|
case "builtin":
|
||||||
|
return todo();
|
||||||
|
}
|
||||||
|
exhausted(rval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private lowerTer(ter: mir.Ter): [lir.Stmt[], lir.Ter] {
|
||||||
|
const tk = ter.kind;
|
||||||
|
switch (tk.tag) {
|
||||||
|
case "unset":
|
||||||
|
return [[], this.ter({ tag: "error" })];
|
||||||
|
case "goto":
|
||||||
|
return [[], this.ter({ tag: "goto", target: tk.target })];
|
||||||
|
case "switch": {
|
||||||
|
const [s1, discr] = this.lowerOperand(tk.discr);
|
||||||
|
return [
|
||||||
|
[...s1],
|
||||||
|
this.ter({
|
||||||
|
tag: "switch",
|
||||||
|
discr,
|
||||||
|
targets: tk.targets
|
||||||
|
.map(({ target, value }) => ({ target, value })),
|
||||||
|
otherwise: tk.otherwise,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "return":
|
||||||
|
return [[], this.ter({ tag: "return" })];
|
||||||
|
case "unreachable":
|
||||||
|
return [[], this.ter({ tag: "error" })];
|
||||||
|
case "drop":
|
||||||
|
return [[], this.ter({ tag: "error" })];
|
||||||
|
}
|
||||||
|
exhausted(tk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private lowerOperand(operand: mir.Operand): [lir.Stmt[], lir.RVal] {
|
||||||
|
switch (operand.tag) {
|
||||||
|
case "error":
|
||||||
|
return [[], { tag: "error" }];
|
||||||
|
case "copy":
|
||||||
|
case "move":
|
||||||
|
return todo(operand.tag);
|
||||||
|
case "const":
|
||||||
|
return [[], { tag: "const", val: operand.val }];
|
||||||
|
}
|
||||||
|
exhausted(operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stmt(kind: lir.StmtKind): lir.Stmt {
|
||||||
|
return { kind };
|
||||||
|
}
|
||||||
|
|
||||||
|
private ter(kind: lir.TerKind): lir.Ter {
|
||||||
|
return { kind };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockHasTarget(block: mir.Block, target: BlockId): boolean {
|
||||||
|
const tk = block.terminator.kind;
|
||||||
|
switch (tk.tag) {
|
||||||
|
case "unset":
|
||||||
|
return false;
|
||||||
|
case "goto":
|
||||||
|
return tk.target == target;
|
||||||
|
case "switch":
|
||||||
|
return tk.targets.some((st) => st.target === target) ||
|
||||||
|
tk.otherwise == target;
|
||||||
|
case "return":
|
||||||
|
return false;
|
||||||
|
case "unreachable":
|
||||||
|
return false;
|
||||||
|
case "drop":
|
||||||
|
return tk.target == target;
|
||||||
|
}
|
||||||
|
exhausted(tk);
|
||||||
|
}
|
@ -1,2 +1,6 @@
|
|||||||
|
export * as mir from "./mir.ts";
|
||||||
|
export * as lir from "./lir.ts";
|
||||||
|
export * as ast_lower from "./ast_lower.ts";
|
||||||
|
export * as mir_lower from "./mir_lower.ts";
|
||||||
|
|
||||||
export * from "./mir.ts";
|
export * from "./mir.ts";
|
||||||
export * from "./ast_lower.ts";
|
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
|
||||||
#[builtin(Hello)]
|
// #[builtin(Hello)]
|
||||||
fn c_hello() -> int {}
|
// fn c_hello() -> int {}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
c_hello();
|
let mut a = 2;
|
||||||
|
if true {
|
||||||
|
a = 3;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
let b = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
127
slige/compiler/stringify/lir.ts
Normal file
127
slige/compiler/stringify/lir.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { BlockId, lir, mir } from "@slige/middle";
|
||||||
|
import { Ctx, exhausted, IdMap, todo } from "@slige/common";
|
||||||
|
import { Ty, tyToString } from "@slige/ty";
|
||||||
|
|
||||||
|
export class LirFnStringifyer {
|
||||||
|
private blockIds = new IdMap<BlockId, number>();
|
||||||
|
private localIds = new IdMap<lir.LocalId, number>();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private ctx: Ctx,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public fn(fn: lir.Fn): string {
|
||||||
|
for (
|
||||||
|
const [idx, id] of fn.blocks
|
||||||
|
.keys()
|
||||||
|
.toArray()
|
||||||
|
.entries()
|
||||||
|
) {
|
||||||
|
this.blockIds.set(id, idx);
|
||||||
|
}
|
||||||
|
for (
|
||||||
|
const [idx, id] of fn.locals
|
||||||
|
.keys()
|
||||||
|
.toArray()
|
||||||
|
.entries()
|
||||||
|
) {
|
||||||
|
this.localIds.set(id, idx);
|
||||||
|
}
|
||||||
|
const blocks = fn.blocks
|
||||||
|
.values()
|
||||||
|
.toArray()
|
||||||
|
.map((block) => this.block(block))
|
||||||
|
.join("\n");
|
||||||
|
return `fn ${fn.mirFn.label} {\n${blocks}\n}`
|
||||||
|
.replaceAll("#", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private block(block: lir.Block): string {
|
||||||
|
const id = this.blockIds.get(block.id);
|
||||||
|
return `#.b${id}: {\n${
|
||||||
|
[
|
||||||
|
...block.stmts
|
||||||
|
.map((stmt) => this.stmt(stmt)),
|
||||||
|
this.ter(block.ter),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
|
}\n#}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private stmt(stmt: lir.Stmt): string {
|
||||||
|
const k = stmt.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "error":
|
||||||
|
return "##<error>;";
|
||||||
|
case "assign":
|
||||||
|
return `##${this.local(k.local)} = ${this.rval(k.rval)}`;
|
||||||
|
}
|
||||||
|
exhausted(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ter(ter: lir.Ter): string {
|
||||||
|
const k = ter.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "error":
|
||||||
|
return "##<error>;";
|
||||||
|
case "goto":
|
||||||
|
return `##goto ${this.blockId(k.target)}`;
|
||||||
|
case "switch": {
|
||||||
|
const discr = this.rval(k.discr);
|
||||||
|
const targets = k.targets
|
||||||
|
.map((target) =>
|
||||||
|
`\n###${target.value} => ${this.blockId(target.target)}`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
const otherwise = this.blockId(k.otherwise);
|
||||||
|
return `##switch ${discr}${targets}\n###_ => ${otherwise}`;
|
||||||
|
}
|
||||||
|
case "return":
|
||||||
|
return `##return;`;
|
||||||
|
}
|
||||||
|
exhausted(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
private rval(rval: lir.RVal): string {
|
||||||
|
switch (rval.tag) {
|
||||||
|
case "error":
|
||||||
|
return "<error>";
|
||||||
|
case "phi":
|
||||||
|
return `phi [${
|
||||||
|
rval.sources
|
||||||
|
.map((src) =>
|
||||||
|
`(${this.blockId(src.branch)}, ${
|
||||||
|
this.local(src.local)
|
||||||
|
})`
|
||||||
|
)
|
||||||
|
.join(",")
|
||||||
|
}]`;
|
||||||
|
case "use":
|
||||||
|
return this.local(rval.local);
|
||||||
|
case "const":
|
||||||
|
return `${this.constVal(rval.val)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private constVal(val: mir.Const): string {
|
||||||
|
switch (val.tag) {
|
||||||
|
case "null":
|
||||||
|
return `null`;
|
||||||
|
case "int":
|
||||||
|
return `${val.value}`;
|
||||||
|
case "str":
|
||||||
|
return `"${val.value}"`;
|
||||||
|
case "fn":
|
||||||
|
return `${val.item.ident.text}`;
|
||||||
|
}
|
||||||
|
exhausted(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private blockId(id: BlockId): string {
|
||||||
|
return `.b${this.blockIds.get(id)!}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private local(local: lir.LocalId): string {
|
||||||
|
return `%${this.localIds.get(local)!}`;
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
export * from "./hir.ts";
|
export * from "./hir.ts";
|
||||||
export * from "./mir.ts";
|
export * from "./mir.ts";
|
||||||
|
export * from "./lir.ts";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user