509 lines
20 KiB
Zig
509 lines
20 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
const print = std.debug.print;
|
|
|
|
const Compilation = @import("../Compilation.zig");
|
|
const Module = Compilation.Module;
|
|
const Package = Compilation.Package;
|
|
|
|
const data_structures = @import("../data_structures.zig");
|
|
const ArrayList = data_structures.ArrayList;
|
|
const BlockList = data_structures.BlockList;
|
|
const AutoHashMap = data_structures.AutoHashMap;
|
|
|
|
pub const Result = struct {
|
|
blocks: BlockList(BasicBlock) = .{},
|
|
calls: BlockList(Call) = .{},
|
|
functions: BlockList(Function) = .{},
|
|
instructions: BlockList(Instruction) = .{},
|
|
jumps: BlockList(Jump) = .{},
|
|
loads: BlockList(Load) = .{},
|
|
phis: BlockList(Phi) = .{},
|
|
stores: BlockList(Store) = .{},
|
|
syscalls: BlockList(Syscall) = .{},
|
|
values: BlockList(Value) = .{},
|
|
stack_references: BlockList(StackReference) = .{},
|
|
};
|
|
|
|
pub fn initialize(compilation: *Compilation, module: *Module, package: *Package, main_file: Compilation.Type.Index) !Result {
|
|
_ = main_file;
|
|
_ = package;
|
|
print("\nFunction count: {}\n", .{module.functions.len});
|
|
|
|
var function_iterator = module.functions.iterator();
|
|
var builder = Builder{
|
|
.allocator = compilation.base_allocator,
|
|
.module = module,
|
|
};
|
|
|
|
while (function_iterator.next()) |sema_function| {
|
|
print("\nFunction: {}\n", .{sema_function});
|
|
|
|
try builder.function(sema_function);
|
|
}
|
|
|
|
return builder.ir;
|
|
}
|
|
|
|
pub const BasicBlock = struct {
|
|
instructions: ArrayList(Instruction.Index) = .{},
|
|
incomplete_phis: ArrayList(Instruction.Index) = .{},
|
|
filled: bool = false,
|
|
sealed: bool = false,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
|
|
fn seal(basic_block: *BasicBlock) void {
|
|
for (basic_block.incomplete_phis.items) |incomplete_phi| {
|
|
_ = incomplete_phi;
|
|
unreachable;
|
|
}
|
|
|
|
basic_block.sealed = true;
|
|
}
|
|
};
|
|
|
|
pub const Instruction = union(enum) {
|
|
call: Call.Index,
|
|
jump: Jump.Index,
|
|
load: Load.Index,
|
|
phi: Phi.Index,
|
|
ret: Ret,
|
|
store: Store.Index,
|
|
syscall: Syscall.Index,
|
|
@"unreachable",
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const Phi = struct {
|
|
value: Value.Index,
|
|
jump: Jump.Index,
|
|
block: BasicBlock.Index,
|
|
next: Phi.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const Ret = struct {
|
|
value: Instruction.Index,
|
|
};
|
|
|
|
pub const Jump = struct {
|
|
source: BasicBlock.Index,
|
|
destination: BasicBlock.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const Syscall = struct {
|
|
arguments: ArrayList(Value.Index),
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const Load = struct {
|
|
value: Value.Index,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const Store = struct {
|
|
source: Value.Index,
|
|
destination: StackReference.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const StackReference = struct {
|
|
size: u64,
|
|
alignment: u64,
|
|
offset: u64,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
const Call = struct {
|
|
function: Function.Index,
|
|
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
pub const Allocation = List.Allocation;
|
|
};
|
|
|
|
pub const Value = union(enum) {
|
|
integer: Integer,
|
|
load: Load.Index,
|
|
call: Call.Index,
|
|
stack_reference: StackReference.Index,
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
|
|
pub fn isInMemory(value: Value) bool {
|
|
return switch (value) {
|
|
.integer => false,
|
|
.load => true,
|
|
.call => true,
|
|
.stack_reference => true,
|
|
};
|
|
}
|
|
};
|
|
|
|
const Integer = struct {
|
|
value: u64,
|
|
sign: bool,
|
|
};
|
|
|
|
const Function = struct {
|
|
blocks: ArrayList(BasicBlock.Index) = .{},
|
|
pub const List = BlockList(@This());
|
|
pub const Index = List.Index;
|
|
};
|
|
|
|
pub const Builder = struct {
|
|
allocator: Allocator,
|
|
ir: Result = .{},
|
|
module: *Module,
|
|
current_basic_block: BasicBlock.Index = BasicBlock.Index.invalid,
|
|
current_function_index: Function.Index = Function.Index.invalid,
|
|
return_phi_node: Instruction.Index = Instruction.Index.invalid,
|
|
current_stack_offset: usize = 0,
|
|
stack_map: AutoHashMap(Compilation.Declaration.Index, StackReference.Index) = .{},
|
|
|
|
fn function(builder: *Builder, sema_function: Compilation.Function) !void {
|
|
builder.current_function_index = (try builder.ir.functions.append(builder.allocator, .{})).index;
|
|
// TODO: arguments
|
|
builder.current_basic_block = try builder.newBlock();
|
|
builder.current_stack_offset = 0;
|
|
builder.stack_map = .{};
|
|
|
|
const return_type = builder.module.types.get(builder.module.function_prototypes.get(sema_function.prototype).return_type);
|
|
const is_noreturn = return_type.* == .noreturn;
|
|
if (!is_noreturn) {
|
|
const exit_block = try builder.newBlock();
|
|
const phi = try builder.ir.phis.addOne(builder.allocator);
|
|
const phi_instruction = try builder.appendToBlock(exit_block, .{
|
|
.phi = phi.index,
|
|
});
|
|
phi.ptr.* = .{
|
|
.value = Value.Index.invalid,
|
|
.jump = Jump.Index.invalid,
|
|
.block = exit_block,
|
|
.next = Phi.Index.invalid,
|
|
};
|
|
const ret = try builder.appendToBlock(exit_block, .{
|
|
.ret = .{
|
|
.value = phi_instruction,
|
|
},
|
|
});
|
|
_ = ret;
|
|
builder.return_phi_node = phi_instruction;
|
|
}
|
|
const sema_block = sema_function.getBodyBlock(builder.module);
|
|
try builder.block(sema_block, .{ .emit_exit_block = !is_noreturn });
|
|
|
|
try builder.dumpFunction(std.io.getStdErr().writer(), builder.current_function_index);
|
|
}
|
|
|
|
fn dumpFunction(builder: *Builder, writer: anytype, index: Function.Index) !void {
|
|
const f = builder.ir.functions.get(index);
|
|
try writer.writeAll("Hello world!\n");
|
|
print("Function blocks: {}\n", .{f.blocks.items.len});
|
|
var function_instruction_index: usize = 0;
|
|
for (f.blocks.items, 0..) |block_index, function_block_index| {
|
|
print("#{}:\n", .{function_block_index});
|
|
const function_block = builder.ir.blocks.get(block_index);
|
|
for (function_block.instructions.items) |instruction_index| {
|
|
const instruction = builder.ir.instructions.get(instruction_index);
|
|
print("%{}: {}\n", .{ function_instruction_index, instruction });
|
|
function_instruction_index += 1;
|
|
}
|
|
|
|
print("\n", .{});
|
|
}
|
|
}
|
|
|
|
fn blockInsideBasicBlock(builder: *Builder, sema_block: *Compilation.Block, block_index: BasicBlock.Index) !BasicBlock.Index {
|
|
builder.current_basic_block = block_index;
|
|
try builder.block(sema_block, .{});
|
|
return builder.current_basic_block;
|
|
}
|
|
|
|
const BlockOptions = packed struct {
|
|
emit_exit_block: bool = true,
|
|
};
|
|
|
|
fn block(builder: *Builder, sema_block: *Compilation.Block, options: BlockOptions) error{OutOfMemory}!void {
|
|
for (sema_block.statements.items) |sema_statement_index| {
|
|
const sema_statement = builder.module.values.get(sema_statement_index);
|
|
switch (sema_statement.*) {
|
|
.loop => |loop_index| {
|
|
const sema_loop = builder.module.loops.get(loop_index);
|
|
const sema_loop_condition = builder.module.values.get(sema_loop.condition);
|
|
const sema_loop_body = builder.module.values.get(sema_loop.body);
|
|
const condition: Compilation.Value.Index = switch (sema_loop_condition.*) {
|
|
.bool => |bool_value| switch (bool_value) {
|
|
true => Compilation.Value.Index.invalid,
|
|
false => unreachable,
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const original_block = builder.current_basic_block;
|
|
const jump_to_loop = try builder.append(.{
|
|
.jump = undefined,
|
|
});
|
|
const loop_body_block = try builder.newBlock();
|
|
const loop_prologue_block = if (options.emit_exit_block) try builder.newBlock() else BasicBlock.Index.invalid;
|
|
|
|
const loop_head_block = switch (condition.valid) {
|
|
false => loop_body_block,
|
|
true => unreachable,
|
|
};
|
|
|
|
builder.ir.instructions.get(jump_to_loop).jump = try builder.jump(.{
|
|
.source = original_block,
|
|
.destination = loop_head_block,
|
|
});
|
|
|
|
const sema_body_block = builder.module.blocks.get(sema_loop_body.block);
|
|
builder.current_basic_block = try builder.blockInsideBasicBlock(sema_body_block, loop_body_block);
|
|
if (loop_prologue_block.valid) {
|
|
builder.ir.blocks.get(loop_prologue_block).seal();
|
|
}
|
|
|
|
if (sema_body_block.reaches_end) {
|
|
_ = try builder.append(.{
|
|
.jump = try builder.jump(.{
|
|
.source = builder.current_basic_block,
|
|
.destination = loop_head_block,
|
|
}),
|
|
});
|
|
}
|
|
|
|
builder.ir.blocks.get(builder.current_basic_block).filled = true;
|
|
builder.ir.blocks.get(loop_body_block).seal();
|
|
if (!loop_head_block.eq(loop_body_block)) {
|
|
unreachable;
|
|
}
|
|
|
|
if (loop_prologue_block.valid) {
|
|
builder.current_basic_block = loop_prologue_block;
|
|
}
|
|
},
|
|
.syscall => |syscall_index| {
|
|
const sema_syscall = builder.module.syscalls.get(syscall_index);
|
|
var arguments = try ArrayList(Value.Index).initCapacity(builder.allocator, sema_syscall.argument_count + 1);
|
|
|
|
const sema_syscall_number = sema_syscall.number;
|
|
assert(sema_syscall_number.valid);
|
|
const number_value_index = try builder.emitValue(sema_syscall_number);
|
|
|
|
arguments.appendAssumeCapacity(number_value_index);
|
|
|
|
for (sema_syscall.getArguments()) |sema_syscall_argument| {
|
|
assert(sema_syscall_argument.valid);
|
|
const argument_value_index = try builder.emitValue(sema_syscall_argument);
|
|
arguments.appendAssumeCapacity(argument_value_index);
|
|
}
|
|
|
|
_ = try builder.append(.{
|
|
.syscall = (try builder.ir.syscalls.append(builder.allocator, .{
|
|
.arguments = arguments,
|
|
})).index,
|
|
});
|
|
},
|
|
.@"unreachable" => _ = try builder.append(.{
|
|
.@"unreachable" = {},
|
|
}),
|
|
.@"return" => |sema_ret_index| {
|
|
const sema_ret = builder.module.returns.get(sema_ret_index);
|
|
const return_value = try builder.emitValue(sema_ret.value);
|
|
const phi_instruction = builder.ir.instructions.get(builder.return_phi_node);
|
|
const phi = builder.ir.phis.get(phi_instruction.phi);
|
|
const exit_jump = try builder.jump(.{ .source = builder.current_basic_block, .destination = phi.block });
|
|
phi_instruction.phi = (try builder.ir.phis.append(builder.allocator, .{
|
|
.value = return_value,
|
|
.jump = exit_jump,
|
|
.next = phi_instruction.phi,
|
|
.block = phi.block,
|
|
})).index;
|
|
|
|
_ = try builder.append(.{
|
|
.jump = exit_jump,
|
|
});
|
|
},
|
|
.declaration => |sema_declaration_index| {
|
|
const sema_declaration = builder.module.declarations.get(sema_declaration_index);
|
|
assert(sema_declaration.scope_type == .local);
|
|
const sema_init_value = builder.module.values.get(sema_declaration.init_value);
|
|
const declaration_type = builder.module.types.get(sema_init_value.getType(builder.module));
|
|
const size = declaration_type.getSize();
|
|
const alignment = declaration_type.getAlignment();
|
|
const stack_offset = switch (size > 0) {
|
|
true => builder.allocateStack(size, alignment),
|
|
false => 0,
|
|
};
|
|
var value_index = try builder.emitValue(sema_declaration.init_value);
|
|
const value = builder.ir.values.get(value_index);
|
|
print("Value: {}\n", .{value.*});
|
|
value_index = switch (value.isInMemory()) {
|
|
false => try builder.load(value_index),
|
|
true => value_index,
|
|
};
|
|
|
|
if (stack_offset > 0) {
|
|
_ = try builder.store(.{
|
|
.source = value_index,
|
|
.destination = try builder.stackReference(stack_offset, declaration_type.*, sema_declaration_index),
|
|
});
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn stackReference(builder: *Builder, stack_offset: u64, t: Compilation.Type, value: Compilation.Declaration.Index) !StackReference.Index {
|
|
const stack_reference_allocation = try builder.ir.stack_references.append(builder.allocator, .{
|
|
.offset = stack_offset,
|
|
.size = t.getSize(),
|
|
.alignment = t.getAlignment(),
|
|
});
|
|
|
|
const index = stack_reference_allocation.index;
|
|
|
|
try builder.stack_map.put(builder.allocator, value, index);
|
|
|
|
return index;
|
|
}
|
|
|
|
fn store(builder: *Builder, descriptor: Store) !void {
|
|
const store_allocation = try builder.ir.stores.append(builder.allocator, descriptor);
|
|
_ = try builder.append(.{
|
|
.store = store_allocation.index,
|
|
});
|
|
}
|
|
|
|
fn allocateStack(builder: *Builder, size: u64, alignment: u64) u64 {
|
|
builder.current_stack_offset = std.mem.alignForward(u64, builder.current_stack_offset, alignment);
|
|
builder.current_stack_offset += size;
|
|
return builder.current_stack_offset;
|
|
}
|
|
|
|
fn load(builder: *Builder, value_index: Value.Index) !Value.Index {
|
|
print("Doing load!\n", .{});
|
|
|
|
const load_allocation = try builder.ir.loads.append(builder.allocator, .{
|
|
.value = value_index,
|
|
});
|
|
const instruction_index = try builder.append(.{
|
|
.load = load_allocation.index,
|
|
});
|
|
_ = instruction_index;
|
|
const result = try builder.ir.values.append(builder.allocator, .{
|
|
.load = load_allocation.index,
|
|
});
|
|
return result.index;
|
|
}
|
|
|
|
fn emitValue(builder: *Builder, sema_value_index: Compilation.Value.Index) !Value.Index {
|
|
const sema_value = builder.module.values.get(sema_value_index).*;
|
|
return switch (sema_value) {
|
|
// TODO
|
|
.integer => |integer| (try builder.ir.values.append(builder.allocator, .{
|
|
.integer = .{
|
|
.value = integer,
|
|
.sign = false,
|
|
},
|
|
})).index,
|
|
.call => |sema_call_index| {
|
|
const sema_call = builder.module.calls.get(sema_call_index);
|
|
const argument_list_index = sema_call.arguments;
|
|
if (argument_list_index.valid) {
|
|
unreachable;
|
|
}
|
|
|
|
const call_index = try builder.call(.{
|
|
.function = switch (builder.module.values.get(sema_call.value).*) {
|
|
.function => |function_index| .{
|
|
.index = function_index.index,
|
|
.block = function_index.block,
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
},
|
|
});
|
|
|
|
_ = try builder.append(.{
|
|
.call = call_index,
|
|
});
|
|
|
|
const value_allocation = try builder.ir.values.append(builder.allocator, .{
|
|
.call = call_index,
|
|
});
|
|
|
|
return value_allocation.index;
|
|
},
|
|
.declaration_reference => |sema_declaration_index| {
|
|
const sema_declaration = builder.module.declarations.get(sema_declaration_index);
|
|
const sema_init_value = builder.module.values.get(sema_declaration.init_value);
|
|
const init_type = sema_init_value.getType(builder.module);
|
|
_ = init_type;
|
|
switch (sema_declaration.scope_type) {
|
|
.local => {
|
|
const stack_reference = builder.stack_map.get(sema_declaration_index).?;
|
|
const value = try builder.ir.values.append(builder.allocator, .{
|
|
.stack_reference = stack_reference,
|
|
});
|
|
return value.index;
|
|
},
|
|
.global => unreachable,
|
|
}
|
|
// switch (sema_declaration.*) {
|
|
// else => |t| @panic(@tagName(t)),
|
|
// }
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
}
|
|
|
|
fn call(builder: *Builder, descriptor: Call) !Call.Index {
|
|
const call_allocation = try builder.ir.calls.append(builder.allocator, descriptor);
|
|
return call_allocation.index;
|
|
}
|
|
|
|
fn jump(builder: *Builder, descriptor: Jump) !Jump.Index {
|
|
const destination_block = builder.ir.blocks.get(descriptor.destination);
|
|
assert(!destination_block.sealed);
|
|
const jump_allocation = try builder.ir.jumps.append(builder.allocator, descriptor);
|
|
return jump_allocation.index;
|
|
}
|
|
|
|
fn append(builder: *Builder, instruction: Instruction) !Instruction.Index {
|
|
assert(builder.current_basic_block.valid);
|
|
return builder.appendToBlock(builder.current_basic_block, instruction);
|
|
}
|
|
|
|
fn appendToBlock(builder: *Builder, block_index: BasicBlock.Index, instruction: Instruction) !Instruction.Index {
|
|
const instruction_allocation = try builder.ir.instructions.append(builder.allocator, instruction);
|
|
try builder.ir.blocks.get(block_index).instructions.append(builder.allocator, instruction_allocation.index);
|
|
|
|
return instruction_allocation.index;
|
|
}
|
|
|
|
fn newBlock(builder: *Builder) !BasicBlock.Index {
|
|
const new_block_allocation = try builder.ir.blocks.append(builder.allocator, .{});
|
|
const current_function = builder.ir.functions.get(builder.current_function_index);
|
|
const function_block_index = current_function.blocks.items.len;
|
|
try current_function.blocks.append(builder.allocator, new_block_allocation.index);
|
|
|
|
print("Adding block: {}\n", .{function_block_index});
|
|
|
|
return new_block_allocation.index;
|
|
}
|
|
};
|