2025-09-10 18:56:45 +10:00
from generators . generator import Generator , SymbolTable
2025-09-01 18:00:49 +10:00
from ground_ast import *
2025-09-13 06:26:15 +10:00
from error import traceback , warning
2025-09-08 07:49:33 +10:00
from optimizers . x86_64 import X86_64Optimizer
2025-09-01 18:00:49 +10:00
class X86_64Generator ( Generator ) :
2025-09-02 06:42:58 +10:00
def __init__ ( self , ast , code , output_path ) :
super ( ) . __init__ ( ast , code , output_path )
self . stack_size = 0
2025-09-10 07:30:58 +10:00
self . function_lines = [ ]
2025-09-02 06:42:58 +10:00
2025-09-01 18:00:49 +10:00
def init ( self ) :
2025-09-07 11:58:01 +10:00
self . lines . append ( " global _start \n " )
2025-09-01 18:00:49 +10:00
self . lines . append ( " _start: \n \t " )
2025-09-02 06:42:58 +10:00
# generate code
2025-09-01 18:00:49 +10:00
self . generate ( )
2025-09-02 06:42:58 +10:00
self . write ( )
2025-09-10 07:30:58 +10:00
def push ( self , reg : str , lines ) :
lines . append ( " push " + reg + " \n \t " )
2025-09-02 06:42:58 +10:00
self . stack_size + = 1
2025-09-10 07:30:58 +10:00
def pop ( self , reg : str , lines ) :
lines . append ( " pop " + reg + " \n \t " )
2025-09-02 06:42:58 +10:00
self . stack_size - = 1
2025-09-10 20:25:19 +10:00
def get_variable ( self , lines , var_name : str , reg : str , float : bool = False , offset : int = 0 , no_stack_pop : bool = True , scope : SymbolTable = None ) :
scope = scope or self . current_var_scope
var = scope . lookup ( var_name )
var_pos = self . get_var_pos ( var_name , scope )
if isinstance ( var_pos , str ) : # in a reg, not on the stack
2025-09-13 07:08:11 +10:00
waste = {
" rax " : " eax " ,
" rdi " : " edi " ,
" rsi " : " esi "
}
if waste [ var_pos ] == reg : # literally useless
return
2025-09-12 06:28:45 +10:00
if offset == 0 :
lines . append ( f " mov { reg } , { var_pos } \n \t " )
elif offset == - 8 :
if var_pos == " rax " : # according to the SysV ABI, the second return value of a function is stored in RDX
var_pos = " rdx "
lines . append ( f " mov { reg } , { var_pos } \n \t " )
2025-09-10 20:25:19 +10:00
else :
try :
if var [ " type " ] == FloatNode :
conversion = {
" rax " : " xmm0 " ,
" rbx " : " xmm1 " ,
" rdi " : " xmm0 "
# ...
}
lines . append ( f " movsd { conversion [ reg ] } , [rsp + { var_pos + offset } ] \n \t " )
lines . append ( " add rsp, 8 \n \t " )
self . stack_size + = 1
elif var [ " type " ] in [ IntNode , StringNode ] :
if no_stack_pop :
lines . append ( f " mov { reg } , [rsp + { var_pos + offset } ] \n \t " )
else :
self . push (
f " QWORD [rsp + { var_pos + offset } ] " ,
lines
)
self . pop ( reg , lines )
elif var [ " type " ] == BoolNode :
if no_stack_pop :
lines . append ( f " mov { reg } , [rsp + { var_pos + offset } ] \n \t " )
else :
self . push (
f " QWORD [rsp + { var_pos + offset } ] " ,
lines
)
self . pop ( reg , lines )
except TypeError as e : # variable doesnt exist
traceback ( self . code , " NameError " , f " \" { var_name } \" is not defined. " )
2025-09-06 21:18:22 +10:00
2025-09-13 06:26:15 +10:00
scope . table [ var_name ] [ " used " ] = True
2025-09-02 19:43:48 +10:00
return var [ " type " ]
2025-09-01 18:00:49 +10:00
2025-09-10 20:25:19 +10:00
def get_var_pos ( self , var_name : str , scope : SymbolTable = None ) :
scope = scope or self . current_var_scope
2025-09-07 07:25:11 +10:00
try :
2025-09-10 20:25:19 +10:00
stack_loc = scope . lookup ( var_name ) [ ' stack_loc ' ]
if isinstance ( stack_loc , str ) :
return stack_loc # its in a reg not on the stack
return ( self . stack_size - stack_loc - 1 ) * 8
except TypeError as e : # not defined
2025-09-07 07:25:11 +10:00
traceback ( self . code , " TypeError " , f " \" { var_name } \" is not defined. " )
2025-09-02 16:57:56 +10:00
2025-09-10 20:25:19 +10:00
def create_variable ( self , lines , var_name : str , starting_value , var_type : Any = None , scope = None ) :
var_type = var_type or type ( starting_value )
scope = scope or self . current_var_scope
2025-09-02 19:43:48 +10:00
2025-09-04 07:45:20 +10:00
stack_location = self . stack_size
if type ( starting_value ) == IntNode :
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rax, { starting_value . value } \n \t " )
self . push ( " rax " , lines )
2025-09-02 16:57:56 +10:00
elif type ( starting_value ) == VarRefNode :
2025-09-10 20:25:19 +10:00
var_type = self . get_variable ( lines , starting_value . var_name , " rax " , scope = scope )
2025-09-04 07:45:20 +10:00
if var_type == FloatNode :
2025-09-10 07:30:58 +10:00
lines . append ( " sub rsp, 8 \n \t " )
lines . append ( " movsd [rsp], xmm0 \n \t " )
2025-09-04 07:45:20 +10:00
else :
2025-09-10 07:30:58 +10:00
self . push ( " rax " , lines )
2025-09-04 07:45:20 +10:00
elif type ( starting_value ) == FloatNode :
name = self . add_constant ( starting_value . value )
2025-09-10 07:30:58 +10:00
lines . append ( " sub rsp, 8 \n \t " ) # make space on the stack
lines . append ( f " movsd xmm0, { name } \n \t " )
lines . append ( " movsd [rsp], xmm0 \n \t " )
2025-09-04 07:45:20 +10:00
self . stack_size + = 1
2025-09-06 20:54:17 +10:00
elif type ( starting_value ) == StringNode :
2025-09-06 21:18:22 +10:00
string_pointer = self . add_constant (
starting_value . value
)
2025-09-06 20:54:17 +10:00
string_len = self . add_constant ( f " equ $ - { string_pointer [ 1 : - 1 ] } " , no_string = True )
2025-09-10 07:30:58 +10:00
lines . append ( f " lea rax, { string_pointer } \n \t " )
self . push ( " rax " , lines )
lines . append ( f " mov rax, { string_len [ 1 : - 1 ] } \n \t " )
self . push ( " rax " , lines )
2025-09-06 20:54:17 +10:00
2025-09-07 13:38:16 +10:00
elif type ( starting_value ) == BoolNode :
2025-09-10 07:30:58 +10:00
self . push ( " 1 " if starting_value . value else " 0 " , lines )
2025-09-07 13:38:16 +10:00
2025-09-02 19:43:48 +10:00
elif type ( starting_value ) == str :
2025-09-04 07:45:20 +10:00
if starting_value . startswith ( " xmm " ) : # floating point stuff
2025-09-10 07:30:58 +10:00
lines . append ( " sub rsp, 8 \n \t " ) # make space
lines . append ( f " movsd [rsp], { starting_value } \n \t " )
2025-09-04 07:45:20 +10:00
self . stack_size + = 1
else :
2025-09-13 15:48:41 +10:00
if starting_value . startswith ( " BUF " ) :
self . push ( starting_value , lines ) # push buffer
self . push ( " rax " , lines ) # push length
else :
self . push ( starting_value , lines )
2025-09-13 06:26:15 +10:00
scope . define ( var_name , { " stack_loc " : stack_location , " type " : var_type , " used " : False } )
2025-09-02 16:57:56 +10:00
2025-09-10 20:25:19 +10:00
def change_variable ( self , lines , var_name : str , new_value , scope : SymbolTable = None ) :
scope = scope or self . current_var_scope
var_pos = self . get_var_pos ( var_name , scope )
2025-09-02 16:26:19 +10:00
2025-09-13 06:26:15 +10:00
old_var_type = scope . table [ var_name ] [ " type " ]
2025-09-10 20:45:22 +10:00
if isinstance ( var_pos , str ) :
if type ( new_value ) == IntNode : # we're changing a variable to a number
#lines.append(f"mov QWORD [rsp + {var_pos}], {new_value.value}\n\t")
lines . append ( f " mov { var_pos } , { new_value . value } \n \t " )
scope . table [ var_name ] [ " type " ] = IntNode
elif type ( new_value ) == VarRefNode : # we're changing a variable to the value of another variable
var_type = self . get_variable ( lines , new_value . var_name , " rax " )
#lines.append(f"mov QWORD [rsp + {var_pos}], rax\n\t")
lines . append ( f " mov { var_pos } , rax \n \t " )
scope . table [ var_name ] [ " type " ] = var_type
elif type ( new_value ) == StringNode : # we're changing a variable to a string
lines . append ( f " mov QWORD [rsp + { var_pos } ], 0 \n \t " )
2025-09-02 16:26:19 +10:00
2025-09-10 20:45:22 +10:00
string_pointer = self . add_constant ( new_value . value )
string_len = self . add_constant ( f " equ $ - { string_pointer [ 1 : - 1 ] } " , no_string = True )
#lines.append(f"lea QWORD [rsp + {var_pos}], {string_pointer}\n\t")
##lines.append(f"mov QWORD [rsp + {var_pos + 8}], {string_len[1:-1]}\n\t")
2025-09-07 07:25:11 +10:00
2025-09-10 20:45:22 +10:00
scope . table [ var_name ] [ " stack_loc " ] = self . stack_size
lines . append ( f " lea rax, { string_pointer } \n \t " )
self . push ( " rax " , lines )
lines . append ( f " mov rax, { string_len [ 1 : - 1 ] } \n \t " )
self . push ( " rax " , lines )
2025-09-07 07:25:11 +10:00
2025-09-10 20:45:22 +10:00
scope . table [ var_name ] [ " type " ] = StringNode
2025-09-07 07:25:11 +10:00
2025-09-10 20:45:22 +10:00
elif type ( new_value ) == BoolNode :
#lines.append(f"mov QWORD [rsp + {var_pos}], {'1' if new_value.value else '0'}\n\t")
lines . append ( f " mov { var_pos } , { int ( new_value . value ) } \n \t " )
scope . table [ var_name ] [ " type " ] = BoolNode
elif type ( new_value ) == str : # we're changing a variable to the value of a register
#lines.append(f"mov QWORD [rsp + {var_pos}], {new_value}\n\t")
lines . append ( f " mov { var_pos } , { new_value } \n \t " )
scope . table [ var_name ] [ " type " ] = IntNode
else :
if type ( new_value ) == IntNode : # we're changing a variable to a number
lines . append ( f " mov QWORD [rsp + { var_pos } ], { new_value . value } \n \t " )
scope . table [ var_name ] [ " type " ] = IntNode
elif type ( new_value ) == VarRefNode : # we're changing a variable to the value of another variable
var_type = self . get_variable ( lines , new_value . var_name , " rax " )
lines . append ( f " mov QWORD [rsp + { var_pos } ], rax \n \t " )
scope . table [ var_name ] [ " type " ] = var_type
elif type ( new_value ) == StringNode : # we're changing a variable to a string
lines . append ( f " mov QWORD [rsp + { var_pos } ], 0 \n \t " )
string_pointer = self . add_constant ( new_value . value )
string_len = self . add_constant ( f " equ $ - { string_pointer [ 1 : - 1 ] } " , no_string = True )
#lines.append(f"lea QWORD [rsp + {var_pos}], {string_pointer}\n\t")
##lines.append(f"mov QWORD [rsp + {var_pos + 8}], {string_len[1:-1]}\n\t")
scope . table [ var_name ] [ " stack_loc " ] = self . stack_size
lines . append ( f " lea rax, { string_pointer } \n \t " )
self . push ( " rax " , lines )
lines . append ( f " mov rax, { string_len [ 1 : - 1 ] } \n \t " )
self . push ( " rax " , lines )
2025-09-07 07:25:11 +10:00
2025-09-10 20:45:22 +10:00
scope . table [ var_name ] [ " type " ] = StringNode
2025-09-02 16:26:19 +10:00
2025-09-10 20:45:22 +10:00
elif type ( new_value ) == BoolNode :
lines . append ( f " mov QWORD [rsp + { var_pos } ], { ' 1 ' if new_value . value else ' 0 ' } \n \t " )
scope . table [ var_name ] [ " type " ] = BoolNode
2025-09-07 13:38:16 +10:00
2025-09-10 20:45:22 +10:00
elif type ( new_value ) == str : # we're changing a variable to the value of a register
lines . append ( f " mov QWORD [rsp + { var_pos } ], { new_value } \n \t " )
scope . table [ var_name ] [ " type " ] = IntNode
2025-09-13 06:26:15 +10:00
if scope . table [ var_name ] [ " type " ] != old_var_type :
warning ( self . code , f " Changing the type of \" { var_name } \" at runtime is considered bad practice. " )
2025-09-07 11:58:01 +10:00
2025-09-10 07:30:58 +10:00
def generate_LabelDecNode ( self , node : LabelDecNode , lines ) :
2025-09-07 11:58:01 +10:00
self . labels . append ( node . name )
2025-09-10 07:30:58 +10:00
lines . append ( " . " + node . name + " : \n \t " )
def generate_FunctionNode ( self , node : FunctionNode , lines ) :
if node . return_type == None :
traceback ( self . code , " SyntaxError " , " Functions require a return type. " )
if node . name == None :
traceback ( self . code , " SyntaxError " , " Functions require a name. " )
2025-09-10 20:25:19 +10:00
func = self . add_function ( node )
self . current_var_scope = func [ " scope " ]
2025-09-10 18:56:45 +10:00
# function boilerplate
2025-09-12 06:28:45 +10:00
self . function_lines . append ( node . name + " : \n \t " )
2025-09-10 18:56:45 +10:00
self . push ( " rbp " , self . function_lines )
self . function_lines . append ( " mov rbp, rsp \n \t " )
2025-09-10 20:25:19 +10:00
for i , arg in enumerate ( node . args ) :
try :
stack_loc = [ " rdi " , " rsi " , " rdx " , " rcx " , " r8 " , " r9 " ] [ i ] # ""
except IndexError :
traceback ( self . code , " CallError " , " Functions with more than 6 args aren ' t supported yet, sorry... " )
2025-09-13 06:26:15 +10:00
self . current_var_scope . define ( arg . name , { " stack_loc " : stack_loc , " type " : self . ground_type_to_node ( arg . arg_type ) , " used " : False } )
2025-09-10 20:25:19 +10:00
2025-09-10 07:30:58 +10:00
for inst in node . statements :
2025-09-13 06:26:15 +10:00
#self.function_lines.append(f"; {inst}\n\t")
2025-09-13 07:08:11 +10:00
node_type = str ( type ( inst ) ) [ 19 : - 2 ]
getattr ( self , f " generate_ { node_type } " ) ( inst , self . function_lines )
#self.generate_InstructionNode(inst, self.function_lines)
2025-09-10 18:56:45 +10:00
2025-09-10 07:30:58 +10:00
def generate_InstructionNode ( self , node : InstructionNode , lines = None ) :
2025-09-10 20:25:19 +10:00
lines = lines or self . lines
2025-09-02 19:43:48 +10:00
2025-09-06 20:54:17 +10:00
### MISC ###
2025-09-01 18:00:49 +10:00
if node . instruction == " end " :
2025-09-08 05:54:13 +10:00
self . clamp_instruction_args ( node , 1 , 1 )
2025-09-04 07:45:20 +10:00
if not type ( node . arguments [ 0 ] ) in [ IntNode , VarRefNode ] : # example: "end true"
2025-09-02 19:43:48 +10:00
traceback ( self . code , " TypeError " , f " end expects an integer, not { node . arguments [ 0 ] } " )
2025-09-01 18:00:49 +10:00
2025-09-07 13:38:16 +10:00
if type ( node . arguments [ 0 ] ) in [ IntNode , BoolNode ] :
2025-09-10 07:30:58 +10:00
lines . append ( " mov rdi, " + str ( node . arguments [ 0 ] . value ) + " \n \t " )
2025-09-04 07:45:20 +10:00
elif isinstance ( node . arguments [ 0 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
var_type = self . get_variable ( lines , node . arguments [ 0 ] . var_name , " rdi " , no_stack_pop = True )
2025-09-04 07:45:20 +10:00
if var_type == FloatNode :
2025-09-10 07:30:58 +10:00
lines . append ( " cvttsd2si rdi, xmm0 \n \t " )
2025-09-04 07:45:20 +10:00
else :
2025-09-07 13:38:16 +10:00
if var_type not in [ IntNode , BoolNode ] :
2025-09-04 07:45:20 +10:00
traceback ( self . code , " TypeError " , f " end expects an integer, not \" { var_type } \" " )
2025-09-13 06:26:15 +10:00
lines . append ( " mov rax, 60 \n \t " )
2025-09-10 07:30:58 +10:00
lines . append ( " syscall \n \t " )
2025-09-02 06:42:58 +10:00
2025-09-06 20:54:17 +10:00
### VARIABLE INSTRUCTIONS ###
2025-09-02 06:42:58 +10:00
elif node . instruction == " set " :
2025-09-07 20:00:35 +10:00
self . clamp_instruction_args ( node , 2 , 2 )
2025-09-02 06:42:58 +10:00
if not isinstance ( node . arguments [ 0 ] , VarPointerNode ) :
2025-09-02 19:43:48 +10:00
traceback ( self . code , " TypeError " , f " the first argument of set should be a variable pointer, not \" { node . arguments [ 0 ] } \" " )
2025-09-07 13:38:16 +10:00
if type ( node . arguments [ 1 ] ) not in [ IntNode , VarRefNode , FloatNode , StringNode , BoolNode ] :
2025-09-02 06:42:58 +10:00
traceback ( self . code , " TypeError " , f " variables can ' t be of type \" { type ( node . arguments [ 1 ] ) } \" " )
2025-09-10 18:56:45 +10:00
variable_exists = self . current_var_scope . lookup ( node . arguments [ 0 ] . var_name ) != None
2025-09-02 16:26:19 +10:00
if not variable_exists : # create a new variable
2025-09-10 07:30:58 +10:00
self . create_variable ( lines , node . arguments [ 0 ] . var_name , node . arguments [ 1 ] )
2025-09-02 16:26:19 +10:00
else : # modify the existing one
2025-09-10 07:30:58 +10:00
self . change_variable ( lines , node . arguments [ 0 ] . var_name , node . arguments [ 1 ] )
2025-09-02 16:26:19 +10:00
2025-09-06 20:54:17 +10:00
### MATH INSTRUCTIONS ###
2025-09-07 20:00:35 +10:00
elif node . instruction in [ " add " , " subtract " , " multiply " ] :
self . clamp_instruction_args ( node , 3 , 3 )
if type ( node . arguments [ 2 ] ) != VarPointerNode :
traceback ( self . code , " TypeError " , f " the destination of the { node . instruction } command must be a variable pointer, not \" { node . arguments [ 2 ] } \" " )
2025-09-02 19:43:48 +10:00
# bro this entire god damn instruction is just error handling 😔
number1_type = None
number2_type = None
arg2 = " rbx "
2025-09-04 07:45:20 +10:00
if isinstance ( node . arguments [ 0 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
number1_type = self . get_variable ( lines , node . arguments [ 0 ] . var_name , " rax " )
2025-09-04 07:45:20 +10:00
elif isinstance ( node . arguments [ 0 ] , FloatNode ) or isinstance ( node . arguments [ 1 ] , FloatNode ) :
number1_type = FloatNode
constant_name = self . add_constant ( node . arguments [ 0 ] . value )
2025-09-10 07:30:58 +10:00
lines . append ( f " movsd xmm0, { constant_name } \n \t " )
2025-09-04 07:45:20 +10:00
elif isinstance ( node . arguments [ 0 ] , IntNode ) :
number1_type = IntNode
2025-09-02 19:43:48 +10:00
#arg1 = node.arguments[0].value
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rax, { node . arguments [ 0 ] . value } \n \t " )
2025-09-02 19:43:48 +10:00
else :
traceback ( self . code , " TypeError " , f " expected a variable reference or number for argument 1 of add, got { node . arguments [ 0 ] } " )
2025-09-04 07:45:20 +10:00
if isinstance ( node . arguments [ 1 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
number2_type = self . get_variable ( lines , node . arguments [ 1 ] . var_name , " rbx " )
2025-09-04 07:45:20 +10:00
elif number1_type == FloatNode or isinstance ( node . arguments [ 1 ] , FloatNode ) :
number2_type = FloatNode
constant_name = self . add_constant ( node . arguments [ 1 ] . value )
2025-09-10 07:30:58 +10:00
lines . append ( f " movsd xmm1, { constant_name } \n \t " )
2025-09-04 07:45:20 +10:00
elif isinstance ( node . arguments [ 1 ] , IntNode ) :
number2_type = IntNode
2025-09-02 19:43:48 +10:00
arg2 = node . arguments [ 1 ] . value
2025-09-10 07:30:58 +10:00
#lines.append(f"mov rbx, {node.arguments[1].value}\n\t")
2025-09-02 19:43:48 +10:00
else :
traceback ( self . code , " TypeError " , f " expected a variable reference or number for argument 2 of add, got { node . arguments [ 1 ] } " )
# TODO: numbers can be added to numbers, but numbers cant be added to strings. but strings can be added to strings, etc...
2025-09-04 07:45:20 +10:00
if number1_type not in [ IntNode , FloatNode ] or number2_type not in [ IntNode , FloatNode ] :
2025-09-07 20:00:35 +10:00
traceback ( self . code , " TypeError " , f " Unsupported operation \" f { node . instruction } \" for \" { node . arguments [ 0 ] } \" and \" { node . arguments [ 1 ] } \" . " )
2025-09-02 19:43:48 +10:00
2025-09-04 07:45:20 +10:00
if number1_type == IntNode and number2_type == IntNode :
2025-09-07 20:00:35 +10:00
if node . instruction == " add " :
2025-09-10 07:30:58 +10:00
lines . append ( f " add rax, { arg2 } \n \t " )
2025-09-07 20:00:35 +10:00
elif node . instruction == " subtract " :
2025-09-10 07:30:58 +10:00
lines . append ( f " sub rax, { arg2 } \n \t " )
2025-09-07 20:00:35 +10:00
elif node . instruction == " multiply " :
2025-09-10 07:30:58 +10:00
lines . append ( f " imul rax, { arg2 } \n \t " )
2025-09-04 07:45:20 +10:00
else :
2025-09-07 20:00:35 +10:00
if node . instruction == " add " :
2025-09-10 07:30:58 +10:00
lines . append ( f " addsd xmm0, xmm1 \n \t " )
2025-09-07 20:00:35 +10:00
elif node . instruction == " subtract " :
2025-09-10 07:30:58 +10:00
lines . append ( f " subsd xmm0, xmm1 \n \t " )
2025-09-07 20:00:35 +10:00
elif node . instruction == " multiply " :
2025-09-10 07:30:58 +10:00
lines . append ( f " mulsd xmm0, xmm1 \n \t " )
2025-09-04 07:45:20 +10:00
is_integer = number1_type == IntNode and number2_type == IntNode
starting_reg = " rax " if is_integer else " xmm0 "
2025-09-02 19:43:48 +10:00
2025-09-10 18:56:45 +10:00
if self . current_var_scope . lookup ( node . arguments [ 2 ] . var_name ) == None : # we need to create a variable for the destination
2025-09-10 07:30:58 +10:00
self . create_variable ( lines , node . arguments [ 2 ] . var_name , starting_reg , IntNode if is_integer else FloatNode )
2025-09-02 19:43:48 +10:00
else :
2025-09-10 07:30:58 +10:00
self . change_variable ( lines , node . arguments [ 2 ] . var_name , starting_reg )
2025-09-06 20:54:17 +10:00
elif node . instruction == " divide " :
2025-09-08 05:54:13 +10:00
self . clamp_instruction_args ( node , 3 , 3 )
if type ( node . arguments [ 2 ] ) != VarPointerNode :
2025-09-06 20:54:17 +10:00
traceback ( self . code , " TypeError " , f " the destination of the divide command must be a variable pointer, not \" { node . arguments [ 2 ] } \" " )
# bro this entire god damn instruction is just error handling 😔
arg2 = " xmm1 "
number1_type = None
number2_type = None
if isinstance ( node . arguments [ 0 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
number1_type = self . get_variable ( lines , node . arguments [ 0 ] . var_name , " rax " , True )
2025-09-06 20:54:17 +10:00
elif type ( node . arguments [ 0 ] ) in [ IntNode , FloatNode ] :
number1_type = FloatNode
constant_name = self . add_constant ( node . arguments [ 0 ] . value )
2025-09-10 07:30:58 +10:00
lines . append ( f " movsd xmm0, { constant_name } \n \t " )
2025-09-06 20:54:17 +10:00
else :
traceback ( self . code , " TypeError " , f " expected a variable reference or number for argument 1 of divide, got { node . arguments [ 0 ] } " )
if isinstance ( node . arguments [ 1 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
number2_type = self . get_variable ( lines , node . arguments [ 1 ] . var_name , " rbx " , True )
2025-09-06 20:54:17 +10:00
elif type ( node . arguments [ 1 ] ) in [ IntNode , FloatNode ] :
number2_type = FloatNode
constant_name = self . add_constant ( node . arguments [ 1 ] . value )
2025-09-10 07:30:58 +10:00
lines . append ( f " movsd xmm1, { constant_name } \n \t " )
2025-09-06 20:54:17 +10:00
else :
traceback ( self . code , " TypeError " , f " expected a variable reference or number for argument 2 of divide, got { node . arguments [ 1 ] } " )
# TODO: numbers can be added to numbers, but numbers cant be added to strings. but strings can be added to strings, etc...
if number1_type not in [ IntNode , FloatNode ] or number2_type not in [ IntNode , FloatNode ] :
traceback ( self . code , " TypeError " , f " Unsupported operation \" divide \" for \" { node . arguments [ 0 ] } \" and \" { node . arguments [ 1 ] } \" . " )
2025-09-10 07:30:58 +10:00
lines . append ( f " divsd xmm0, xmm1 \n \t " )
2025-09-06 20:54:17 +10:00
2025-09-10 18:56:45 +10:00
if self . current_var_scope . lookup ( node . arguments [ 2 ] . var_name ) == None : # we need to create a variable for the destination
2025-09-10 07:30:58 +10:00
self . create_variable ( lines , node . arguments [ 2 ] . var_name , " xmm0 " , FloatNode )
2025-09-06 20:54:17 +10:00
else :
2025-09-10 07:30:58 +10:00
self . change_variable ( lines , node . arguments [ 2 ] . var_name , " xmm0 " )
2025-09-06 20:54:17 +10:00
elif node . instruction == " stdout " :
2025-09-08 05:54:13 +10:00
self . clamp_instruction_args ( node , 1 , 1 )
2025-09-06 20:54:17 +10:00
arg = node . arguments [ 0 ]
printed_value = arg . __str__ ( )
if isinstance ( arg , VarRefNode ) :
2025-09-10 07:30:58 +10:00
var_type = self . get_variable ( lines , arg . var_name , " rsi " , offset = 0 , no_stack_pop = True )
2025-09-07 07:25:11 +10:00
if var_type == StringNode :
2025-09-10 07:30:58 +10:00
self . get_variable ( lines , arg . var_name , " rdx " , offset = - 8 , no_stack_pop = True )
2025-09-07 07:25:11 +10:00
else :
traceback ( self . code , " TypeError " , f " You can ' t print \" { var_type ( None ) . __repr__ ( ) } \" , try converting it to a string first. " )
2025-09-06 20:54:17 +10:00
else :
string_pointer = self . add_constant ( printed_value ) [ 1 : - 1 ]
string_len = self . add_constant ( f " equ $ - { string_pointer } " , True ) [ 1 : - 1 ]
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rsi, { string_pointer } \n \t " )
lines . append ( f " mov rdx, { string_len } \n \t " ) # length
2025-09-06 20:54:17 +10:00
2025-09-10 20:25:19 +10:00
self . push ( " rdi " , lines ) # save rdi
2025-09-10 07:30:58 +10:00
lines . append ( " mov rax, 1 \n \t " ) # sys_write syscall
lines . append ( " mov rdi, 1 \n \t " ) # a file descriptor of 1 is stdout
lines . append ( " syscall \n \t " )
2025-09-10 20:25:19 +10:00
self . pop ( " rdi " , lines ) # restore rdi
2025-09-02 19:43:48 +10:00
2025-09-13 15:48:41 +10:00
elif node . instruction == " stdin " :
self . clamp_instruction_args ( node , 1 , 1 )
arg = node . arguments [ 0 ]
if not isinstance ( arg , VarPointerNode ) :
traceback ( self . code , " TypeError " , f " Argument 1 of stdin has to be a variable pointer, not \" { arg } \" " )
buf_size = 255
buffer = self . add_buffer ( buf_size )
lines . append ( " mov rax, 0 \n \t " ) # sys_read syscall
lines . append ( " mov rdi, 0 \n \t " ) # a file descriptor of 0 is stdin
lines . append ( f " mov rsi, { buffer } \n \t " )
lines . append ( f " mov rdx, { buf_size } " )
lines . append ( " syscall " )
if not self . current_var_scope . table . get ( arg . var_name ) :
self . create_variable ( lines , arg . var_name , buffer , StringNode )
else :
self . change_variable ( lines , arg . var_name , buffer )
2025-09-07 11:58:01 +10:00
elif node . instruction == " jump " :
2025-09-08 05:54:13 +10:00
self . clamp_instruction_args ( node , 1 , 1 )
if not isinstance ( node . arguments [ 0 ] , LabelRefNode ) :
2025-09-07 11:58:01 +10:00
traceback ( self . code , " TypeError " , f " jump expects a label reference as the first argument, not \" { node . arguments [ 0 ] } \" " )
2025-09-10 07:30:58 +10:00
lines . append ( f " jmp . { node . arguments [ 0 ] . name } \n \t " )
2025-09-07 13:38:16 +10:00
elif node . instruction == " if " :
2025-09-08 05:54:13 +10:00
self . clamp_instruction_args ( node , 2 , 2 )
if not type ( node . arguments [ 0 ] ) in [ VarRefNode , BoolNode , StringNode , FloatNode , IntNode ] :
2025-09-07 13:38:16 +10:00
traceback ( self . code , " TypeError " , f " if expects a value or variable refernce as the first argument, not \" { node . arguments [ 0 ] } \" " )
elif not isinstance ( node . arguments [ 1 ] , LabelRefNode ) :
traceback ( self . code , " TypeError " , f " if expects a label reference as the second argument, not \" { node . arguments [ 1 ] } \" " )
if isinstance ( node . arguments [ 0 ] , BoolNode ) :
if node . arguments [ 0 ] . value :
2025-09-10 07:30:58 +10:00
lines . append ( f " jmp . { node . arguments [ 1 ] . name } \n \t " )
#lines.append("mov eax, 1")
#lines.append(f"cmp eax, {1 if node.arguments[0].value else 0}")
2025-09-07 13:38:16 +10:00
elif type ( node . arguments [ 0 ] ) in [ IntNode , FloatNode ] :
if node . arguments [ 0 ] . value != 0 :
2025-09-10 07:30:58 +10:00
lines . append ( f " jmp . { node . arguments [ 1 ] . name } \n \t " )
2025-09-07 13:38:16 +10:00
elif isinstance ( node . arguments [ 0 ] , VarRefNode ) :
2025-09-13 07:08:11 +10:00
self . current_var_scope . table [ node . arguments [ 0 ] . var_name ] [ " used " ] = True # putting a variable in an if statement means we're accessing it
2025-09-10 07:30:58 +10:00
self . get_variable ( lines , node . arguments [ 0 ] . var_name , " eax " )
lines . append ( f " test eax, eax \n \t " )
lines . append ( f " jnz . { node . arguments [ 1 ] . name } \n \t " )
2025-09-08 05:50:43 +10:00
elif node . instruction in [ " equal " , " inequal " , " greater " , " lesser " ] :
2025-09-08 05:54:13 +10:00
self . clamp_instruction_args ( node , 3 , 3 )
if not type ( node . arguments [ 0 ] ) in [ VarRefNode , BoolNode , FloatNode , IntNode ] :
traceback ( self . code , " TypeError " , f " { node . instruction } expects a value or variable refernce as the first argument, not \" { node . arguments [ 0 ] . __repr__ ( ) } \" " )
2025-09-07 13:38:16 +10:00
elif not type ( node . arguments [ 1 ] ) in [ VarRefNode , BoolNode , FloatNode , IntNode ] :
2025-09-08 05:54:13 +10:00
traceback ( self . code , " TypeError " , f " { node . instruction } expects a value or variable refernce as the second argument, not \" { node . arguments [ 1 ] . __repr__ ( ) } \" " )
2025-09-07 13:38:16 +10:00
elif not isinstance ( node . arguments [ 2 ] , VarPointerNode ) :
2025-09-08 05:54:13 +10:00
traceback ( self . code , " TypeError " , f " the third argument of { node . instruction } should be a variable pointer, not \" { node . arguments [ 2 ] . __repr__ ( ) } \" " )
2025-09-07 13:38:16 +10:00
arg1 = None
arg2 = None
if isinstance ( node . arguments [ 0 ] , BoolNode ) :
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rax, { int ( node . arguments [ 0 ] . value ) } \n \t " )
2025-09-07 13:38:16 +10:00
arg1 = " rax "
elif isinstance ( node . arguments [ 0 ] , IntNode ) :
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rax, { node . arguments [ 0 ] . value } \n \t " )
2025-09-07 13:38:16 +10:00
arg1 = " rax "
elif isinstance ( node . arguments [ 0 ] , FloatNode ) :
const_name = self . add_constant ( node . arguments [ 0 ] . value )
2025-09-10 07:30:58 +10:00
lines . append ( f " movsd xmm0, { const_name } \n \t " )
2025-09-07 13:38:16 +10:00
arg1 = " xmm0 "
elif isinstance ( node . arguments [ 0 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
self . get_variable ( lines , node . arguments [ 0 ] . var_name , " rax " )
2025-09-07 13:38:16 +10:00
arg1 = " rax "
if isinstance ( node . arguments [ 1 ] , BoolNode ) :
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rbx, { int ( node . arguments [ 1 ] . value ) } \n \t " )
2025-09-07 13:38:16 +10:00
arg2 = " rbx "
elif isinstance ( node . arguments [ 1 ] , IntNode ) :
2025-09-10 07:30:58 +10:00
lines . append ( f " mov rbx, { node . arguments [ 1 ] . value } \n \t " )
2025-09-07 13:38:16 +10:00
arg2 = " rbx "
elif isinstance ( node . arguments [ 1 ] , FloatNode ) :
const_name = self . add_constant ( node . arguments [ 1 ] . value )
2025-09-10 07:30:58 +10:00
lines . append ( f " movsd xmm1, { const_name } \n \t " )
2025-09-07 13:38:16 +10:00
arg2 = " xmm1 "
elif isinstance ( node . arguments [ 1 ] , VarRefNode ) :
2025-09-10 07:30:58 +10:00
self . get_variable ( lines , node . arguments [ 1 ] . var_name , " rbx " )
2025-09-07 13:38:16 +10:00
arg2 = " rbx "
2025-09-10 07:30:58 +10:00
lines . append ( f " cmp { arg1 } , { arg2 } \n \t " )
2025-09-07 13:38:16 +10:00
2025-09-08 05:50:43 +10:00
instructions = {
" equal " : " sete " ,
" inequal " : " setne " ,
" greater " : " setg " ,
" lesser " : " setl "
}
2025-09-10 07:30:58 +10:00
lines . append ( f " { instructions [ node . instruction ] } al \n \t " )
2025-09-13 07:08:11 +10:00
lines . append ( " movzx eax, al \n \t " )
2025-09-07 13:38:16 +10:00
var_name = node . arguments [ 2 ] . var_name
2025-09-10 18:56:45 +10:00
if self . current_var_scope . lookup ( var_name ) == None :
2025-09-10 07:30:58 +10:00
self . create_variable ( lines , var_name , " rax " , BoolNode )
2025-09-07 13:38:16 +10:00
else :
2025-09-10 07:30:58 +10:00
self . change_variable ( lines , var_name , " rax " )
2025-09-13 19:29:06 +10:00
elif node . instruction == " not " :
self . clamp_instruction_args ( node , 2 , 2 )
if type ( node . arguments [ 0 ] ) not in [ VarRefNode , BoolNode ] :
traceback ( self . code , " TypeError " , f " Argument 1 of \" not \" must be a boolean or variable reference, not \" { node . arguments [ 0 ] } \" " )
elif not isinstance ( node . arguments [ 1 ] , VarPointerNode ) :
traceback ( self . code , " TypeError " , f " Argument 2 of \" not \" must be a variable pointer, not \" { node . arguments [ 1 ] } \" " )
result = " "
if isinstance ( node . arguments [ 0 ] , BoolNode ) :
result = BoolNode ( not node . arguments [ 0 ] . value )
elif isinstance ( node . arguments [ 0 ] , VarRefNode ) :
var_type = self . get_variable ( lines , node . arguments [ 0 ] . var_name , " rax " )
if var_type != BoolNode :
traceback ( self . code , " TypeError " , f " Argument 1 of \" not \" must be a boolean or variable reference, not \" { var_type ( 0 ) . __repr__ ( ) } \" " )
result = " rax "
lines . append ( " xor rax, 1 \n \t " )
if self . current_var_scope . table . get ( node . arguments [ 1 ] . var_name ) :
self . change_variable ( lines , node . arguments [ 1 ] . var_name , result )
else :
self . create_variable ( lines , node . arguments [ 1 ] . var_name , result , BoolNode )
2025-09-10 07:30:58 +10:00
elif node . instruction == " endfun " :
return
elif node . instruction == " return " :
2025-09-10 07:54:11 +10:00
self . clamp_instruction_args ( node , 0 , 1 )
2025-09-10 20:25:19 +10:00
func = self . functions . get ( node . parent . name ) # the return statement will be a parent of a function node, so we can get the current scope using that
2025-09-10 07:54:11 +10:00
2025-09-12 06:28:45 +10:00
func_scope : SymbolTable = func [ " scope " ]
2025-09-10 07:54:11 +10:00
if len ( node . arguments ) == 1 :
if isinstance ( node . arguments [ 0 ] , IntNode ) :
lines . append ( f " mov rax, { node . arguments [ 0 ] . value } " )
elif isinstance ( node . arguments [ 0 ] , BoolNode ) :
2025-09-13 07:08:11 +10:00
lines . append ( f " mov eax, { int ( node . arguments [ 0 ] . value ) } " )
2025-09-10 07:54:11 +10:00
elif isinstance ( node . arguments [ 0 ] , FloatNode ) :
lines . append ( f " mov xmm0, { node . arguments [ 0 ] . value } " )
2025-09-10 18:56:45 +10:00
#self.get_variable(lines, node.arguments[0].var_name, "rax")
2025-09-12 06:28:45 +10:00
elif isinstance ( node . arguments [ 0 ] , StringNode ) :
string_pointer = self . add_constant ( node . arguments [ 0 ] . value )
length = self . add_constant ( f " equ $ - { string_pointer [ 1 : - 1 ] } " , no_string = True )
lines . append ( f " lea rax, [rel { string_pointer [ 1 : - 1 ] } ] \n \t " )
2025-09-13 06:26:15 +10:00
lines . append ( f " mov rdx, { length [ 1 : - 1 ] } \n \t " )
2025-09-12 06:28:45 +10:00
#self.push("rax", lines)
2025-09-10 20:25:19 +10:00
elif isinstance ( node . arguments [ 0 ] , VarRefNode ) :
2025-09-12 06:28:45 +10:00
var = func_scope . lookup ( node . arguments [ 0 ] . var_name )
if var [ " type " ] == StringNode :
self . get_variable ( self . function_lines , node . arguments [ 0 ] . var_name , " rax " , offset = 0 , scope = func [ " scope " ] )
self . get_variable ( self . function_lines , node . arguments [ 0 ] . var_name , " rdx " , offset = - 8 , scope = func [ " scope " ] )
2025-09-13 07:08:11 +10:00
elif var [ " type " ] == BoolNode :
self . get_variable ( self . function_lines , node . arguments [ 0 ] . var_name , " eax " , scope = func [ " scope " ] )
2025-09-12 06:28:45 +10:00
else :
self . get_variable ( self . function_lines , node . arguments [ 0 ] . var_name , " rax " , scope = func [ " scope " ] )
2025-09-10 07:54:11 +10:00
else :
lines . append ( " mov rax, 0 \n \t " )
2025-09-13 06:26:15 +10:00
size = 0
for name , var in self . current_var_scope . table . items ( ) :
if type ( var [ " stack_loc " ] ) == str :
continue # its in a register, we dont need to free up the stack
if var [ " type " ] == StringNode :
size + = 2
else :
size + = 1
lines . append ( f " add rsp, { 8 * ( size ) } \n \t " )
self . stack_size - = size - 1
lines . append ( " pop rbp " )
2025-09-10 07:30:58 +10:00
lines . append ( " ret \n \t " )
2025-09-13 06:26:15 +10:00
# warn about unused variables
for name , var in self . current_var_scope . table . items ( ) :
if not var [ " used " ] :
warning ( self . code , f " \" { name } \" was defined but never used. " )
2025-09-10 18:56:45 +10:00
old_scope = self . current_var_scope
self . current_var_scope = self . current_var_scope . parent
del old_scope
2025-09-10 07:30:58 +10:00
elif node . instruction == " call " :
2025-09-10 07:54:11 +10:00
self . clamp_instruction_args ( node , 1 , 2 )
2025-09-10 07:30:58 +10:00
if not isinstance ( node . arguments [ 0 ] , FunctionCallNode ) :
2025-09-12 06:28:45 +10:00
traceback ( self . code , " TypeError " , " Argument 2 of call needs to be a function reference. " )
2025-09-10 07:30:58 +10:00
2025-09-10 18:56:45 +10:00
func = self . functions . get ( node . arguments [ 0 ] . func_name , None )
if not func :
traceback ( self . code , " TypeError " , f " Function \" { node . arguments [ 0 ] . func_name } \" is not defined. " )
2025-09-10 20:25:19 +10:00
func_scope : SymbolTable = func [ " scope " ]
func : FunctionNode = func [ " func " ]
2025-09-10 18:56:45 +10:00
if len ( self . arg_list ) != len ( func . args ) :
traceback ( self . code , " TypeError " , f " Function \" { node . arguments [ 0 ] . func_name } \" takes { len ( func . args ) } arguments, but got { len ( self . arg_list ) } " )
# stack alignment
2025-09-13 06:26:15 +10:00
if self . stack_size % 2 != 0 :
2025-09-10 18:56:45 +10:00
lines . append ( " sub rsp, 8 \n \t " ) # align the stack to 16 bytes
2025-09-12 06:28:45 +10:00
self . stack_size - = 1
2025-09-10 18:56:45 +10:00
for i , arg in enumerate ( self . arg_list ) :
#self.create_variable(lines, func.args[i].name, arg, func.args[i].arg_type)
value = " "
2025-09-10 20:25:19 +10:00
var_type = " "
2025-09-10 18:56:45 +10:00
if isinstance ( arg , IntNode ) :
value = arg . value
2025-09-10 20:25:19 +10:00
var_type = IntNode
2025-09-10 18:56:45 +10:00
elif isinstance ( arg , StringNode ) :
2025-09-10 20:25:19 +10:00
2025-09-10 18:56:45 +10:00
value = self . add_constant ( arg . value )
else :
traceback ( self . code , " CallError " , f " Can ' t pass { arg } to function. " )
2025-09-10 20:25:19 +10:00
arg_name = func . args [ i ] . name
#self.change_variable(lines, arg_name, value, func_scope)
2025-09-10 18:56:45 +10:00
if i == 0 :
2025-09-10 20:25:19 +10:00
#func_scope.define(arg_name, {"stack_loc": "rdi", "type": type(arg)})
2025-09-10 18:56:45 +10:00
lines . append ( f " mov rdi, { value } " )
elif i == 1 :
lines . append ( f " mov rsi, { value } " )
elif i == 2 :
lines . append ( f " mov rdx, { value } " )
elif i == 3 :
lines . append ( f " mov rcx, { value } " )
elif i == 4 :
lines . append ( f " mov r8, { value } " )
elif i == 5 :
lines . append ( f " mov r9, { value } " )
else :
traceback ( self . code , " CallError " , " Functions with more than 6 args aren ' t supported yet, sorry... " )
2025-09-10 07:30:58 +10:00
lines . append ( f " call { node . arguments [ 0 ] . func_name } \n \t " )
2025-09-10 18:56:45 +10:00
2025-09-13 06:26:15 +10:00
#if len(self.arg_list) > 0:
# lines.append(f"add rsp, {len(self.arg_list) * 8}")
2025-09-12 06:28:45 +10:00
self . stack_size + = len ( self . arg_list )
2025-09-10 18:56:45 +10:00
self . arg_list . clear ( )
2025-09-10 07:54:11 +10:00
if len ( node . arguments ) == 2 :
2025-09-10 18:56:45 +10:00
if not isinstance ( node . arguments [ 1 ] , VarPointerNode ) :
traceback ( self . code , " TypeError " , " Argument 1 of call needs to be a variable pointer. " )
if self . current_var_scope . lookup ( node . arguments [ 1 ] . var_name ) :
2025-09-10 07:54:11 +10:00
self . change_variable ( lines , node . arguments [ 1 ] . var_name , " rax " )
else :
2025-09-13 06:26:15 +10:00
self . current_var_scope . define ( node . arguments [ 1 ] . var_name , { " stack_loc " : " rax " , " type " : self . ground_type_to_node ( func . return_type ) , " used " : False } )
2025-09-12 06:28:45 +10:00
#self.create_variable(lines, node.arguments[1].var_name, "rax", self.ground_type_to_node(self.functions.get(node.arguments[0].func_name)["func"].return_type))
2025-09-10 18:56:45 +10:00
elif node . instruction == " pusharg " :
self . clamp_instruction_args ( node , 1 , 1 )
#if type(node.arguments[0]) not in [IntNode]:
# traceback(self.code, "TypeError", f"A {node.arguments[0]} can't be passed as an argument.")
2025-09-07 13:38:16 +10:00
2025-09-10 18:56:45 +10:00
self . arg_list . append ( node . arguments [ 0 ] )
2025-09-02 06:42:58 +10:00
else :
2025-09-09 17:47:43 +10:00
raise NotImplementedError ( f " A generate method hasn ' t been made for the \" { node . instruction } \" instruction. " )
2025-09-04 07:45:20 +10:00
2025-09-07 13:38:16 +10:00
2025-09-04 07:45:20 +10:00
def write ( self ) :
with open ( self . output_path + " .asm " , " w " ) as f :
2025-09-06 21:18:22 +10:00
f . write ( " ; ~~~ Auto generated by the GroundPY compiler for Linux x86_64 targets. ~~~ \n \n " )
2025-09-04 07:45:20 +10:00
f . write ( " section .data \n " )
2025-09-06 20:54:17 +10:00
for name , const in self . constants . items ( ) :
value = const [ " value " ]
2025-09-13 07:08:11 +10:00
f . write ( name + " : " )
2025-09-04 07:45:20 +10:00
value_type = type ( value )
if value_type == str :
2025-09-06 20:54:17 +10:00
if not const [ " no_string " ] :
2025-09-13 19:29:22 +10:00
value = value . replace ( " \" " , " \" , 34, \" " )
value = value . replace ( " \r " , " \" , 13, \" " )
value = value . replace ( " \n " , " \" , 10, \" " )
value = value . replace ( " \a " , " \" , 7, \" " )
final = f ' db " ' + value + " \" , 0 "
2025-09-10 07:30:58 +10:00
final = final . replace ( " , \" \" , " , " , " )
f . write ( final )
2025-09-06 20:54:17 +10:00
else :
f . write ( value )
2025-09-04 07:45:20 +10:00
elif value_type == float or value_type == int :
f . write ( f " dq { float ( value ) } " )
f . write ( " \n " )
2025-09-13 15:48:41 +10:00
f . write ( " section .bss \n " )
for buf , size in self . buffers . items ( ) :
f . write ( f " { buf } resb { size } \n " )
2025-09-07 11:58:01 +10:00
f . write ( " section .text \n " )
2025-09-08 07:49:33 +10:00
2025-09-10 07:30:58 +10:00
optimizer = X86_64Optimizer ( self . lines + self . function_lines )
2025-09-09 07:20:50 +10:00
f . writelines ( optimizer . optimize_until_stable ( ) )