aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fibonacci.hgn22
-rw-r--r--src/Lexer.zig3
-rw-r--r--src/codegen.zig26
-rw-r--r--src/compile.zig222
-rw-r--r--src/main.zig6
-rw-r--r--src/parse.zig73
6 files changed, 232 insertions, 120 deletions
diff --git a/fibonacci.hgn b/fibonacci.hgn
index 01d9333..c42241b 100644
--- a/fibonacci.hgn
+++ b/fibonacci.hgn
@@ -1,11 +1,13 @@
-a := 0
-b := 1
-n := 10
-# n := read_int(0)
-while n > 0 {
- c := a + b
- a = b
- b = c
- n = n - 1
+main := proc() {
+ a := 0
+ b := 1
+ n := 10
+ # n := read_int(0)
+ while n > 0 {
+ c := a + b
+ a = b
+ b = c
+ n = n - 1
+ }
+ print(a)
}
-print(a)
diff --git a/src/Lexer.zig b/src/Lexer.zig
index 4b664fe..87ced92 100644
--- a/src/Lexer.zig
+++ b/src/Lexer.zig
@@ -26,6 +26,7 @@ pub const Token = struct {
@"if",
@"else",
@"while",
+ proc,
};
};
@@ -137,7 +138,7 @@ fn identifierOrKeyword(self: *Self) Token {
}
const value = self.source[self.start..self.pos];
return self.create(switch (std.meta.stringToEnum(Token.Type, value) orelse .invalid) {
- .@"if", .@"else", .@"while" => |t| t,
+ .@"if", .@"else", .@"while", .proc => |t| t,
else => .identifier,
});
}
diff --git a/src/codegen.zig b/src/codegen.zig
index a1c2171..00b3857 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -563,7 +563,7 @@ const Relocation = struct {
const Context = struct {
instructions: std.ArrayList(Instruction),
relocations: std.ArrayList(Relocation),
- block_starts: std.ArrayList(usize),
+ block_addrs: std.AutoHashMap(compile.BlockRef, usize),
print_block: compile.BlockRef,
read_int_block: compile.BlockRef,
@@ -767,7 +767,7 @@ const ProcedureContext = struct {
fn codegenProc(self: *Self, proc: compile.Procedure) !void {
for (proc.blocks) |block| {
- try self.ctx.block_starts.append(self.ctx.instructions.items.len);
+ try self.ctx.block_addrs.putNoClobber(block.ref, self.ctx.instructions.items.len);
try self.codegenBlock(block);
}
}
@@ -845,17 +845,23 @@ fn codegenReadInt(self: *Context) !void {
try self.emit(.jalr(.zero, .ra, 0));
}
-pub fn create_elf(allocator: Allocator, proc: compile.Procedure) ![]u8 {
+pub fn create_elf(allocator: Allocator, mod: compile.Module) ![]u8 {
+ var maxBlockRef: usize = 0;
+ for (mod.procedures) |proc| {
+ for (proc.blocks) |block| {
+ maxBlockRef = @max(maxBlockRef, @intFromEnum(block.ref));
+ }
+ }
var ctx: Context = .{
.instructions = .init(allocator),
.relocations = .init(allocator),
- .block_starts = .init(allocator),
- .print_block = @enumFromInt(proc.blocks.len),
- .read_int_block = @enumFromInt(proc.blocks.len + 1),
+ .block_addrs = .init(allocator),
+ .print_block = @enumFromInt(maxBlockRef + 1),
+ .read_int_block = @enumFromInt(maxBlockRef + 2),
};
defer ctx.deinit();
- {
+ for (mod.procedures) |proc| {
var proc_ctx: ProcedureContext = .{
.register_allocator = try .init(allocator, &.{ .t6, .t5, .t4, .t3, .t2, .t1, .t0 }),
.lvar_allocator = try .init(allocator, &.{ .s11, .s10, .s9, .s8, .s7, .s6, .s5, .s4, .s3, .s2, .s1, .s0 }),
@@ -867,15 +873,15 @@ pub fn create_elf(allocator: Allocator, proc: compile.Procedure) ![]u8 {
std.debug.assert(proc_ctx.register_allocator.allocated.count() == 0);
}
- try ctx.block_starts.append(ctx.instructions.items.len);
+ try ctx.block_addrs.putNoClobber(ctx.print_block, ctx.instructions.items.len);
try codegenPrint(&ctx);
- try ctx.block_starts.append(ctx.instructions.items.len);
+ try ctx.block_addrs.putNoClobber(ctx.read_int_block, ctx.instructions.items.len);
try codegenReadInt(&ctx);
// TODO: make this less sheiße
for (ctx.relocations.items) |relocation| {
const instr = &ctx.instructions.items[relocation.instr];
- const target: isize = @intCast(ctx.block_starts.items[@intFromEnum(relocation.target)]);
+ const target: isize = @intCast(ctx.block_addrs.get(relocation.target).?);
const from: isize = @intCast(relocation.instr);
switch (instr.*) {
.j => |j| {
diff --git a/src/compile.zig b/src/compile.zig
index 583be50..d2226f1 100644
--- a/src/compile.zig
+++ b/src/compile.zig
@@ -30,6 +30,77 @@ fn IdCounter(Id: type) type {
};
}
+pub const Module = struct {
+ procedures: []Procedure,
+
+ pub fn format(self: Module, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ _ = .{ fmt, options };
+ for (self.procedures) |proc| {
+ try writer.print("{}", .{proc});
+ }
+ }
+};
+
+pub const Procedure = struct {
+ name: []const u8,
+ blocks: []BasicBlock,
+
+ fn init(allocator: Allocator, name: []const u8, blocks: []BasicBlock) !Procedure {
+ for (blocks) |*block| {
+ try block.finalize(allocator);
+ }
+
+ return .{ .name = name, .blocks = blocks };
+ }
+
+ pub fn format(self: Procedure, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ _ = .{ fmt, options };
+ try writer.print("{s}:\n", .{self.name});
+ for (self.blocks, 0..) |block, i| {
+ try writer.print(" ${}{}", .{ i, block });
+ }
+ try writer.writeAll("\n");
+ }
+};
+
+pub const BasicBlock = struct {
+ ref: BlockRef,
+ instrs: std.ArrayListUnmanaged(Instr) = .empty,
+
+ vreg_last_use: std.AutoHashMapUnmanaged(VReg, usize) = .empty,
+
+ fn finalize(self: *BasicBlock, allocator: Allocator) !void {
+ std.debug.assert(self.instrs.items.len > 0);
+ std.debug.assert(switch (self.instrs.getLast().type) {
+ inline else => |ty| @hasDecl(@TypeOf(ty), "may_end_block"),
+ });
+
+ self.vreg_last_use = .empty;
+ for (0.., self.instrs.items) |i, instr| {
+ for (instr.sources().slice()) |src|
+ try self.vreg_last_use.put(allocator, src, i);
+ if (instr.dest()) |dest|
+ try self.vreg_last_use.put(allocator, dest, i);
+ }
+ }
+
+ fn immediate_successors(self: *BasicBlock) std.BoundedArray(BlockRef, 2) {
+ return switch (self.instrs.getLast().type) {
+ .branch => |branch| std.BoundedArray(BlockRef, 2).fromSlice(&.{ branch.true.to, branch.false.to }) catch unreachable,
+ .jump => |jump| std.BoundedArray(BlockRef, 2).fromSlice(&.{jump.to}) catch unreachable,
+ else => .{},
+ };
+ }
+
+ pub fn format(self: BasicBlock, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ _ = .{ fmt, options };
+ try writer.print(":\n", .{});
+ for (self.instrs.items) |instr| {
+ try writer.print(" {}\n", .{instr});
+ }
+ }
+};
+
pub const Instr = struct {
loc: Location,
type: Type,
@@ -199,95 +270,76 @@ pub const Instr = struct {
}
};
-pub const Procedure = struct {
- blocks: []BasicBlock,
-
- fn init(allocator: Allocator, blocks: []BasicBlock) !Procedure {
- for (blocks) |*block| {
- try block.finalize(allocator);
- }
-
- return .{ .blocks = blocks };
- }
-
- pub fn format(self: Procedure, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
- _ = .{ fmt, options };
- for (self.blocks, 0..) |block, i| {
- try writer.print("${}{}", .{ i, block });
- }
- }
+const Scope = struct {
+ locals: std.StringHashMapUnmanaged(LVar),
+ parent: ?*Scope,
};
-pub const BasicBlock = struct {
- instrs: std.ArrayListUnmanaged(Instr) = .empty,
-
- vreg_last_use: std.AutoHashMapUnmanaged(VReg, usize) = .empty,
-
- fn finalize(self: *BasicBlock, allocator: Allocator) !void {
- std.debug.assert(self.instrs.items.len > 0);
- std.debug.assert(switch (self.instrs.getLast().type) {
- inline else => |ty| @hasDecl(@TypeOf(ty), "may_end_block"),
- });
-
- self.vreg_last_use = .empty;
- for (0.., self.instrs.items) |i, instr| {
- for (instr.sources().slice()) |src|
- try self.vreg_last_use.put(allocator, src, i);
- if (instr.dest()) |dest|
- try self.vreg_last_use.put(allocator, dest, i);
- }
- }
+const CompileError = error{
+ OutOfMemory,
+ CanOnlyCallIdentifiers,
+ UnknownProcedure,
+ UnknownVariable,
+ CannotDefineProcedureHere,
+};
- fn immediate_successors(self: *BasicBlock) std.BoundedArray(BlockRef, 2) {
- return switch (self.instrs.getLast().type) {
- .branch => |branch| std.BoundedArray(BlockRef, 2).fromSlice(&.{ branch.true.to, branch.false.to }) catch unreachable,
- .jump => |jump| std.BoundedArray(BlockRef, 2).fromSlice(&.{jump.to}) catch unreachable,
- else => .{},
- };
+pub fn compile(allocator: Allocator, source: []const u8, file: parse.File) !Module {
+ const procs = try allocator.alloc(Procedure, file.decls.len);
+ var ctx: Context = .{
+ .allocator = allocator,
+ .block_ctx = .init,
+ };
+ for (procs, file.decls) |*proc, decl| {
+ proc.* = try compileProcedure(
+ &ctx,
+ source,
+ decl.inner.ident,
+ decl.inner.value.type.proc,
+ );
}
+ return .{
+ .procedures = procs,
+ };
+}
- pub fn format(self: BasicBlock, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
- _ = .{ fmt, options };
- try writer.print(":\n", .{});
- for (self.instrs.items) |instr| {
- try writer.print(" {}\n", .{instr});
- }
- }
+const Context = struct {
+ allocator: Allocator,
+ block_ctx: IdCounter(BlockRef),
};
-pub fn compile(allocator: Allocator, source: []const u8, block: parse.Block) !Procedure {
- var ctx: CompileContext = .{
- .allocator = allocator,
+fn compileProcedure(
+ ctx: *Context,
+ source: []const u8,
+ name: Location,
+ proc: parse.Expr.Type.Proc,
+) !Procedure {
+ var pctx: ProcedureContext = .{
+ .ctx = ctx,
.source = source,
.vreg_ctr = .init,
.lvar_ctr = .init,
.scope = .{ .locals = .empty, .parent = null },
.blocks = .empty,
- .current_block = @enumFromInt(0),
+ .current_block = undefined, // immediately set by `switchToNewBlock`
};
- _ = try ctx.switchToNewBlock();
- try ctx.compileBlock(block);
- try ctx.addInstr(.{
+ _ = try pctx.switchToNewBlock();
+ try pctx.compileBlock(proc.body);
+ try pctx.addInstr(.{
.loc = .{ .start = 0, .end = 0 },
.type = .{ .exit = .{} },
});
- return try .init(allocator, try ctx.blocks.toOwnedSlice(allocator));
+ var blocks = try ctx.allocator.alloc(BasicBlock, pctx.blocks.count());
+ var it = pctx.blocks.iterator();
+ var i: usize = 0;
+ while (it.next()) |kv| : (i += 1) {
+ std.debug.assert(kv.key_ptr.* == kv.value_ptr.ref);
+ blocks[i] = kv.value_ptr.*;
+ }
+ return try .init(ctx.allocator, name.getIdent(source), blocks);
}
-const Scope = struct {
- locals: std.StringHashMapUnmanaged(LVar),
- parent: ?*Scope,
-};
-
-const CompileError = error{
- OutOfMemory,
- CanOnlyCallIdentifiers,
- UnknownProcedure,
- UnknownVariable,
-};
-
-const CompileContext = struct {
- allocator: Allocator,
+const ProcedureContext = struct {
+ ctx: *Context,
source: []const u8,
vreg_ctr: IdCounter(VReg),
@@ -295,10 +347,10 @@ const CompileContext = struct {
scope: Scope,
- blocks: std.ArrayListUnmanaged(BasicBlock),
+ blocks: std.AutoArrayHashMapUnmanaged(BlockRef, BasicBlock),
current_block: BlockRef,
- fn compileBlock(self: *CompileContext, block: parse.Block) !void {
+ fn compileBlock(self: *ProcedureContext, block: parse.Block) !void {
var parent = self.scope;
self.scope = .{
.locals = .empty,
@@ -309,11 +361,11 @@ const CompileContext = struct {
try self.compileStmt(stmt);
}
- self.scope.locals.deinit(self.allocator);
+ self.scope.locals.deinit(self.ctx.allocator);
self.scope = parent;
}
- fn compileStmt(self: *CompileContext, stmt: parse.Stmt) CompileError!void {
+ fn compileStmt(self: *ProcedureContext, stmt: parse.Stmt) CompileError!void {
switch (stmt.type) {
.expr => |expr| _ = try self.compileExpr(expr),
.block => |block| {
@@ -323,7 +375,7 @@ const CompileContext = struct {
const local = if (assign_var.is_decl) blk: {
const local = self.lvar_ctr.get();
const name = assign_var.ident.getIdent(self.source);
- try self.scope.locals.put(self.allocator, name, local);
+ try self.scope.locals.put(self.ctx.allocator, name, local);
break :blk local;
} else blk: {
var scope: ?*Scope = &self.scope;
@@ -373,7 +425,7 @@ const CompileContext = struct {
}
}
- fn compileExpr(self: *CompileContext, expr: *const parse.Expr) !VReg {
+ fn compileExpr(self: *ProcedureContext, expr: *const parse.Expr) !VReg {
// This is not used by all expression types, but creating an unused virtual register is not a problem.
const dest = self.vreg_ctr.get();
switch (expr.type) {
@@ -470,19 +522,23 @@ const CompileContext = struct {
self.current_block = after;
},
+ .proc => |proc| {
+ _ = proc;
+ return error.CannotDefineProcedureHere;
+ },
}
return dest;
}
- fn switchToNewBlock(self: *CompileContext) !BlockRef {
- const ref: BlockRef = @enumFromInt(self.blocks.items.len);
- try self.blocks.append(self.allocator, .{});
+ fn switchToNewBlock(self: *ProcedureContext) !BlockRef {
+ const ref = self.ctx.block_ctx.get();
+ try self.blocks.putNoClobber(self.ctx.allocator, ref, .{ .ref = ref });
self.current_block = ref;
return ref;
}
- fn addInstr(self: *CompileContext, instr: Instr) !void {
- try self.blocks.items[@intFromEnum(self.current_block)]
- .instrs.append(self.allocator, instr);
+ fn addInstr(self: *ProcedureContext, instr: Instr) !void {
+ try self.blocks.getPtr(self.current_block).?
+ .instrs.append(self.ctx.allocator, instr);
}
};
diff --git a/src/main.zig b/src/main.zig
index 5873c66..55b7e0e 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -47,9 +47,9 @@ pub fn main() !void {
if (lexer.peek().type != .eof) {
std.debug.print("Unexpected token {}, expected end of file\n", .{lexer.next()});
}
- const procedure = try compile.compile(allocator, source, ast);
- std.debug.print("Bytecode instructions:\n{}", .{procedure});
- const elf = try codegen.create_elf(allocator, procedure);
+ const module = try compile.compile(allocator, source, ast);
+ std.debug.print("Bytecode instructions:\n{}", .{module});
+ const elf = try codegen.create_elf(allocator, module);
try out_file.writer().writeAll(elf);
std.debug.print("Run output:\n", .{});
if (run) {
diff --git a/src/parse.zig b/src/parse.zig
index bd6b000..bd0ca46 100644
--- a/src/parse.zig
+++ b/src/parse.zig
@@ -23,6 +23,25 @@ pub fn fmt(tree: anytype, source: []const u8, indent: usize) Fmt(@TypeOf(tree))
return .{ .data = .{ tree, source, indent } };
}
+pub const File = struct {
+ decls: []Decl,
+
+ const Decl = struct {
+ loc: Lexer.Location,
+ inner: Stmt.Type.AssignVar,
+ };
+
+ fn format(self: File, writer: anytype, source: []const u8, indent: usize) !void {
+ for (self.decls) |decl| {
+ try writer.print("{s} {s} {}", .{
+ decl.inner.ident.getIdent(source),
+ ":=",
+ fmt(decl.inner.value, source, indent),
+ });
+ }
+ }
+};
+
pub const Block = struct {
loc: Lexer.Location,
stmts: []Stmt,
@@ -87,6 +106,7 @@ pub const Expr = struct {
call: Call,
identifier,
@"if": If,
+ proc: Proc,
pub const BinOp = struct {
lhs: *const Expr,
@@ -124,6 +144,10 @@ pub const Expr = struct {
then: Block,
@"else": ?Block,
};
+
+ pub const Proc = struct {
+ body: Block,
+ };
};
fn format(self: Expr, writer: anytype, source: []const u8, indent: usize) !void {
@@ -145,6 +169,9 @@ pub const Expr = struct {
try writer.print(" else {}", .{fmt(@"else", source, indent)});
}
},
+ .proc => |proc| {
+ try writer.print("proc() {}", .{fmt(proc.body, source, indent)});
+ },
}
}
};
@@ -157,22 +184,28 @@ const ParseError = error{
ExprStatementMustBeCall,
};
-pub fn file(allocator: Allocator, lexer: *Lexer) !Block {
- var stmts: std.ArrayList(Stmt) = .init(allocator);
+pub fn file(allocator: Allocator, lexer: *Lexer) !File {
+ var decls: std.ArrayList(File.Decl) = .init(allocator);
while (lexer.peek().type != .eof) {
- try stmts.append(try statement(allocator, lexer));
+ const stmt = try parseStatement(allocator, lexer);
+ if (stmt.type != .assign_var or !stmt.type.assign_var.is_decl) {
+ return error.ExpectedProcedureDeclaration;
+ }
+ try decls.append(.{
+ .loc = stmt.loc,
+ .inner = stmt.type.assign_var,
+ });
}
return .{
- .loc = stmts.items[0].loc.combine(stmts.getLast().loc),
- .stmts = try stmts.toOwnedSlice(),
+ .decls = try decls.toOwnedSlice(),
};
}
-fn block(allocator: Allocator, lexer: *Lexer) !Block {
+fn parseBlock(allocator: Allocator, lexer: *Lexer) !Block {
const left_curly = try mustEat(lexer, .left_curly);
var stmts: std.ArrayList(Stmt) = .init(allocator);
while (lexer.peek().type != .right_curly) {
- try stmts.append(try statement(allocator, lexer));
+ try stmts.append(try parseStatement(allocator, lexer));
}
const right_curly = try mustEat(lexer, .right_curly);
return .{
@@ -181,10 +214,10 @@ fn block(allocator: Allocator, lexer: *Lexer) !Block {
};
}
-fn statement(allocator: Allocator, lexer: *Lexer) ParseError!Stmt {
+fn parseStatement(allocator: Allocator, lexer: *Lexer) ParseError!Stmt {
switch (lexer.peek().type) {
.left_curly => {
- const b = try block(allocator, lexer);
+ const b = try parseBlock(allocator, lexer);
return .{
.loc = b.loc,
.type = .{ .block = b },
@@ -193,7 +226,7 @@ fn statement(allocator: Allocator, lexer: *Lexer) ParseError!Stmt {
.@"while" => {
const @"while" = lexer.next();
const cond = try expression(allocator, lexer);
- const do = try block(allocator, lexer);
+ const do = try parseBlock(allocator, lexer);
return .{
.loc = @"while".loc.combine(do.loc),
.type = .{ .@"while" = .{
@@ -232,7 +265,21 @@ fn statement(allocator: Allocator, lexer: *Lexer) ParseError!Stmt {
}
fn expression(allocator: Allocator, lexer: *Lexer) ParseError!*Expr {
- return parseComparisons(allocator, lexer);
+ return parseProc(allocator, lexer);
+}
+
+fn parseProc(allocator: Allocator, lexer: *Lexer) ParseError!*Expr {
+ if (lexer.peek().type != .proc) return parseComparisons(allocator, lexer);
+ const proc = try mustEat(lexer, .proc);
+ _ = try mustEat(lexer, .left_paren);
+ // TODO: parameters
+ _ = try mustEat(lexer, .right_paren);
+ const body = try parseBlock(allocator, lexer);
+
+ return allocate(Expr, allocator, .{
+ .loc = proc.loc.combine(body.loc),
+ .type = .{ .proc = .{ .body = body } },
+ });
}
fn parseComparisons(allocator: Allocator, lexer: *Lexer) ParseError!*Expr {
@@ -278,10 +325,10 @@ fn parseIf(allocator: Allocator, lexer: *Lexer) !*Expr {
.@"if" => {
const @"if" = lexer.next();
const cond = try expression(allocator, lexer);
- const then = try block(allocator, lexer);
+ const then = try parseBlock(allocator, lexer);
const @"else" = if (lexer.peek().type == .@"else") blk: {
_ = lexer.next();
- break :blk try block(allocator, lexer);
+ break :blk try parseBlock(allocator, lexer);
} else null;
return try allocate(Expr, allocator, .{
.loc = @"if".loc.combine((@"else" orelse then).loc),