(* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. *) module import common, cstdlib, cstdio, cstring, lexer, ast, symbol, tac var qbe_unique_id: Word := 0u proc qbe_write_slice(file: ^FILE; text: []const Word8) begin fwrite#[Word8](text.ptr, 1u, text.length, file) return proc qbe_write_str(file: ^FILE; str: ^const Word8) begin fwrite#[Word8](str, 1u, strlen(str), file) return proc qbe_write_word(file: ^FILE; val: Word) var buf: [32]Word8 len: Int begin len := sprintf(@buf[1], "%u\0".ptr, val); fwrite#[Word8](@buf[1], 1u, cast(len: Word), file) return proc is_global(globals: ^ElnaTacStaticVariable; name: []const Word8) -> Bool var current: ^ElnaTacStaticVariable found: Bool := false begin current := globals; while current <> nil & ~found do if current^.name = name then found := true else current := current^.next end end return found proc is_string_constant(strings: ^ElnaTacStringConstant; name: []const Word8) -> Bool var current: ^ElnaTacStringConstant found: Bool := false begin current := strings; while current <> nil & ~found do if current^.name = name then found := true else current := current^.next end end return found proc is_pointer_type(_type: ^ElnaType) -> Bool var result: Bool := false begin if _type <> nil then if _type^.kind = ElnaTypeKind.pointer then result := true elsif _type = pointer_type then result := true end end return result proc get_procedure_return_type(proc_name: []const Word8) -> ^ElnaType var symbol_info: ^ElnaSymbolInfo procedure_info: ^ElnaSymbolProcedureInfo result: ^ElnaType := nil begin symbol_info := elna_symbol_table_lookup(symbol_table_global, proc_name); if symbol_info <> nil & symbol_info^.kind = ElnaSymbolInfoKind.procedure_info then procedure_info := cast(symbol_info: ^ElnaSymbolProcedureInfo); if procedure_info^.procedure_type <> nil then result := procedure_info^.procedure_type^.return_type end end return result proc get_param_type(sym_table: ^ElnaSymbolTable; param_name: []const Word8) -> ^ElnaType var symbol_info: ^ElnaSymbolInfo temp_info: ^ElnaSymbolTemporaryInfo result: ^ElnaType := nil begin symbol_info := elna_symbol_table_lookup(sym_table, param_name); if symbol_info <> nil & symbol_info^.kind = ElnaSymbolInfoKind.temporary_info then temp_info := cast(symbol_info: ^ElnaSymbolTemporaryInfo); result := temp_info^.variable_type end return result proc is_pointer_operand(op: ^ElnaTacOperand; sym_table: ^ElnaSymbolTable; globals: ^ElnaTacStaticVariable; strings: ^ElnaTacStringConstant) -> Bool var var_type: ^ElnaType result: Bool := false begin if op^.kind = ElnaTacKind.variable then if is_global(globals, op^.name) or is_string_constant(strings, op^.name) then result := true else var_type := get_param_type(sym_table, op^.name); if is_pointer_type(var_type) then result := true end end end return result proc qbe_emit_operand(file: ^FILE; op: ^ElnaTacOperand; globals: ^ElnaTacStaticVariable; strings: ^ElnaTacStringConstant) begin case op^.kind of ElnaTacKind.constant: qbe_write_word(file, op^.constant) | ElnaTacKind.variable: if is_global(globals, op^.name) or is_string_constant(strings, op^.name) then qbe_write_str(file, "$\0".ptr) else qbe_write_str(file, "%\0".ptr) end; qbe_write_slice(file, op^.name) | ElnaTacKind.label: qbe_write_str(file, "@\0".ptr); qbe_write_slice(file, op^.name) else end return proc var_needs_stack_alloc(instructions: ^ElnaList; sym_table: ^ElnaSymbolTable; name: []const Word8) -> Bool var node: ^ElnaListNode inst: ^ElnaTacInstruction var_type: ^ElnaType result: Bool := false begin var_type := get_param_type(sym_table, name); if var_type <> nil then if var_type^.kind = ElnaTypeKind._record or var_type^.kind = ElnaTypeKind.array then result := true end end; node := instructions^.first; while node <> nil & ~result do inst := cast(node: ^ElnaTacInstruction); if inst^.operator = ElnaTacOperator.get_address & inst^.operands[1].name = name then result := true elsif inst^.operator = ElnaTacOperator.copy_to_offset & inst^.operands[2].name = name then result := true elsif inst^.operator = ElnaTacOperator.copy_from_offset & inst^.operands[1].name = name then result := true end; node := node^.next end return result proc qbe_emit_procedure(file: ^FILE; proc_node: ^ElnaTacProcedure; globals: ^ElnaTacStaticVariable; strings: ^ElnaTacStringConstant) var node: ^ElnaListNode inst: ^ElnaTacInstruction ret_type: ^ElnaType param_idx: Word := 0u param_name: []const Word8 param_type: ^ElnaType entry: ^ElnaSymbolEntry temp_info: ^ElnaSymbolTemporaryInfo sym_idx: Word := 0u alloc_size: Word last_operator: ElnaTacOperator := ElnaTacOperator.nop tid: Word arg_i: Word arg_op: ^ElnaTacOperand callee_ret: ^ElnaType scaled: Word begin qbe_write_str(file, "export function \0".ptr); ret_type := get_procedure_return_type(proc_node^.name); if proc_node^.name = "main" then qbe_write_str(file, "w \0".ptr) elsif ret_type <> nil then if ret_type = bool_type then qbe_write_str(file, "w \0".ptr) else qbe_write_str(file, "l \0".ptr) end end; qbe_write_str(file, "$\0".ptr); qbe_write_slice(file, proc_node^.name); qbe_write_str(file, "(\0".ptr); while param_idx < proc_node^.count do param_name := (proc_node^.parameters + param_idx)^; param_type := get_param_type(proc_node^.symbol_table, param_name); if param_type = bool_type then qbe_write_str(file, "w %\0".ptr) else qbe_write_str(file, "l %\0".ptr) end; qbe_write_slice(file, param_name); if param_idx + 1u < proc_node^.count then qbe_write_str(file, ", \0".ptr) end; param_idx := param_idx + 1u end; qbe_write_str(file, ") {\n@start\n\0".ptr); (* Stack allocate variables needing memory address *) if proc_node^.symbol_table <> nil then while sym_idx < proc_node^.symbol_table^.count do entry := proc_node^.symbol_table^.symbols + sym_idx; if entry^.name.length > 0u & entry^.symbol_info <> nil then if entry^.symbol_info^.kind = ElnaSymbolInfoKind.temporary_info then temp_info := cast(entry^.symbol_info: ^ElnaSymbolTemporaryInfo); if var_needs_stack_alloc(@proc_node^.body, proc_node^.symbol_table, entry^.name) then alloc_size := 8u; if temp_info^.variable_type <> nil & temp_info^.variable_type^.size > 8u then alloc_size := (temp_info^.variable_type^.size + 7u) / 8u * 8u end; qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, entry^.name); qbe_write_str(file, " =l alloc8 \0".ptr); qbe_write_word(file, alloc_size); qbe_write_str(file, "\n\0".ptr) end end end; sym_idx := sym_idx + 1u end end; (* Emit TAC instructions *) node := proc_node^.body.first; while node <> nil do inst := cast(node: ^ElnaTacInstruction); last_operator := inst^.operator; case inst^.operator of ElnaTacOperator.get_address: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, " =l copy \0".ptr); if is_global(globals, inst^.operands[1].name) or is_string_constant(strings, inst^.operands[1].name) then qbe_write_str(file, "$\0".ptr) else qbe_write_str(file, "%\0".ptr) end; qbe_write_slice(file, inst^.operands[1].name); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.copy: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, " =l copy \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.add: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.subtract: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l sub \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.multiply: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l mul \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.divide: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l div \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.remainder: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l rem \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator._or: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l or \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator._xor: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l xor \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.and: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l and \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.equal: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =w ceql \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.not_equal: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =w cnew \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.less_than: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =w csltl \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.greater_than: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =w csgtl \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.less_or_equal: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =w cslel \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.greater_or_equal: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =w csgel \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.negate: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, " =l neg \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.complement: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, " =l xor \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", -1\n\0".ptr) | ElnaTacOperator.load: qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, " =l loadl \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.store: qbe_write_str(file, "\tstorel \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.copy_to_offset: if inst^.operands[3].constant = 0u then qbe_write_str(file, "\tstorel \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr) else qbe_unique_id := qbe_unique_id + 1u; tid := qbe_unique_id; qbe_write_str(file, "\t%qbe_off_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_write_word(file, inst^.operands[3].constant); qbe_write_str(file, "\n\tstorel \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", %qbe_off_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n\0".ptr) end | ElnaTacOperator.copy_from_offset: if inst^.operands[2].constant = 0u then qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l loadl \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, "\n\0".ptr) else qbe_unique_id := qbe_unique_id + 1u; tid := qbe_unique_id; qbe_write_str(file, "\t%qbe_off_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_write_word(file, inst^.operands[2].constant); qbe_write_str(file, "\n\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); qbe_write_str(file, " =l loadl %qbe_off_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n\0".ptr) end | ElnaTacOperator.add_ptr: qbe_unique_id := qbe_unique_id + 1u; tid := qbe_unique_id; if inst^.operands[2].kind = ElnaTacKind.constant then if inst^.operands[3].constant = 1u then qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[4].name); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_write_word(file, inst^.operands[2].constant); qbe_write_str(file, "\n\0".ptr) else scaled := inst^.operands[2].constant * inst^.operands[3].constant; qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[4].name); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", \0".ptr); qbe_write_word(file, scaled); qbe_write_str(file, "\n\0".ptr) end else qbe_write_str(file, "\t%qbe_ext_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, " =l extuw \0".ptr); qbe_emit_operand(file, @inst^.operands[2], globals, strings); qbe_write_str(file, "\n\0".ptr); if inst^.operands[3].constant = 1u then qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[4].name); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", %qbe_ext_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n\0".ptr) else qbe_write_str(file, "\t%qbe_s_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, " =l mul %qbe_ext_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, ", \0".ptr); qbe_write_word(file, inst^.operands[3].constant); qbe_write_str(file, "\n\t%\0".ptr); qbe_write_slice(file, inst^.operands[4].name); qbe_write_str(file, " =l add \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", %qbe_s_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n\0".ptr) end end | ElnaTacOperator.proc_call: callee_ret := get_procedure_return_type(inst^.operands[1].name); if inst^.operands[3].kind = ElnaTacKind.ignore then qbe_write_str(file, "\tcall $\0".ptr) else qbe_write_str(file, "\t%\0".ptr); qbe_write_slice(file, inst^.operands[3].name); if callee_ret = bool_type then qbe_write_str(file, " =w call $\0".ptr) else qbe_write_str(file, " =l call $\0".ptr) end end; qbe_write_slice(file, inst^.operands[1].name); qbe_write_str(file, "(\0".ptr); arg_i := 0u; while arg_i < inst^.operands[2].length do arg_op := inst^.operands[2].list_operands + arg_i; qbe_write_str(file, "l \0".ptr); qbe_emit_operand(file, arg_op, globals, strings); if arg_i + 1u < inst^.operands[2].length then qbe_write_str(file, ", \0".ptr) end; arg_i := arg_i + 1u end; qbe_write_str(file, ")\n\0".ptr) | ElnaTacOperator.jump: qbe_write_str(file, "\tjmp @\0".ptr); qbe_write_slice(file, inst^.operands[1].name); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.jump_if_zero: qbe_unique_id := qbe_unique_id + 1u; tid := qbe_unique_id; qbe_write_str(file, "\tjnz \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", @qbe_ft_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, ", @\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, "\n@qbe_ft_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.jump_if_not_zero: qbe_unique_id := qbe_unique_id + 1u; tid := qbe_unique_id; qbe_write_str(file, "\tjnz \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, ", @\0".ptr); qbe_write_slice(file, inst^.operands[2].name); qbe_write_str(file, ", @qbe_ft_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n@qbe_ft_\0".ptr); qbe_write_word(file, tid); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator.label: qbe_write_str(file, "@\0".ptr); qbe_write_slice(file, inst^.operands[1].name); qbe_write_str(file, "\n\0".ptr) | ElnaTacOperator._return: if inst^.operands[1].kind = ElnaTacKind.ignore then qbe_write_str(file, "\tret\n\0".ptr) else qbe_write_str(file, "\tret \0".ptr); qbe_emit_operand(file, @inst^.operands[1], globals, strings); qbe_write_str(file, "\n\0".ptr) end else end; node := node^.next end; (* Function terminator if not terminated *) if last_operator <> ElnaTacOperator._return & last_operator <> ElnaTacOperator.jump then if proc_node^.name = "main" then qbe_write_str(file, "\tret 0\n\0".ptr) elsif ret_type = nil then qbe_write_str(file, "\tret\n\0".ptr) else qbe_write_str(file, "\tret 0\n\0".ptr) end end; qbe_write_str(file, "}\n\n\0".ptr) return proc elna_qbe_emit*(instruction_module: ^ElnaInstructionModule; out_file: ^FILE) var current_string: ^ElnaTacStringConstant current_global: ^ElnaTacStaticVariable current_proc: ^ElnaTacProcedure i: Word byte_val: Word begin (* Emit strings *) current_string := instruction_module^.strings; while current_string <> nil do qbe_write_str(out_file, "data $\0".ptr); qbe_write_slice(out_file, current_string^.name); qbe_write_str(out_file, " = { \0".ptr); i := 0u; while i < current_string^.value.length do qbe_write_str(out_file, "b \0".ptr); byte_val := cast((current_string^.value.ptr + i)^: Word); qbe_write_word(out_file, byte_val); qbe_write_str(out_file, ", \0".ptr); i := i + 1u end; qbe_write_str(out_file, "b 0 }\n\0".ptr); current_string := current_string^.next end; (* Emit global data *) current_global := instruction_module^.data; while current_global <> nil do qbe_write_str(out_file, "export data $\0".ptr); qbe_write_slice(out_file, current_global^.name); qbe_write_str(out_file, " = { z \0".ptr); qbe_write_word(out_file, current_global^.size); qbe_write_str(out_file, " }\n\0".ptr); current_global := current_global^.next end; (* Emit procedures *) current_proc := instruction_module^.code; while current_proc <> nil do qbe_emit_procedure(out_file, current_proc, instruction_module^.data, instruction_module^.strings); current_proc := current_proc^.next end return end.