class Stacklang::Function

Overview

TODO handle copy with index in a temporary register with a beq loop ?

Included Modules

Defined in:

stacklang/compiler/function/assembly.cr
stacklang/compiler/function/assignment.cr
stacklang/compiler/function/binary.cr
stacklang/compiler/function/call.cr
stacklang/compiler/function/compile.cr
stacklang/compiler/function/expressions.cr
stacklang/compiler/function/function.cr
stacklang/compiler/function/leaf.cr
stacklang/compiler/function/move.cr
stacklang/compiler/function/statements.cr
stacklang/compiler/function/unary.cr

Constant Summary

GPR = [Registers::R1, Registers::R2, Registers::R3, Registers::R4, Registers::R5, Registers::R6]

Register free to be used for temporary values and cache

RETURN_ADRESS_REGISTER = Registers::R6

Per ABI

STACK_REGISTER = Registers::R7

Per ABI

Constructors

Instance Method Summary

Constructor Detail

def self.new(ast : Stacklang::AST::Function, unit : Stacklang::Unit) #

Compute the prototype of the function. Example of a stack frame for a simple function: fun foobar(param1, param2):_ { var a; }

+----------------------+ <- Stack Pointer (R7) value within function | a | +----------------------+ | param1 | +----------------------+ | param2 | +----------------------+ <- Used internaly to store return address | reserved (always) | +----------------------+ | return value | +----------------------+ <- Stack Pointer (R7) value from caller


[View source]

Instance Method Detail

def add(a : Registers, b : Registers, c : Registers) #

[View source]
def addi(a : Registers, b : Registers, imm : Int32 | String | Memory) #

[View source]
def assemble_immediate(immediate : Int32 | String | Memory, kind : Kind) #

Helper function for assembling immediate value. It provide a value for the immediate, or store the reference for linking if the value is a symbol.


[View source]

[View source]
def beq(a : Registers, b : Registers, imm : Int32 | String | Memory) #

[View source]
def cache(variable, excludes) : Registers #

Cache a variable in a register. Used to fetch temporary variables, or to get the actual value of a variable. If the variable can be cached (restricted or temporary), the register is kept linked to the variable so next time we need the value of the variable, we can reuse this register. If the register is needed for something else, it will be automatically unlinked and persisted to ram (by #grab_register).


[View source]
def check_termination(body : Array(AST::Statement)) #

Check a block to ensure that it terminates.


[View source]
def compile : RiSC16::Object::Section #

Generate the section representing the instructions for the compiled functions.

TODO find a way to ensure every path end with a return.


[View source]
def compile_access(access : AST::Access, into : Registers | Memory | Nil) : Type::Any #

Compile the value of an access and move it's value if necessary.


[View source]
def compile_access_lvalue(access : AST::Access) : Tuple(Memory, Type::Any) | Nil #

Get the memory location and type represented an access. This work by obtaining a memory location for its subvalue and adding the accessed field offset.


[View source]
def compile_addition(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST, soustract = false) : Type::Any #

[View source]
def compile_addressable_unary(operand : AST::Expression, into : Registers | Memory | Nil, node : AST) : Type::Any #

Compile &() expression.


[View source]
def compile_any_unary(unary : AST::Unary, into : Registers | Memory | Nil) : Type::Any #

Compile a unary operator value, and move it's value if necessary.


[View source]
def compile_assignment(left_side : AST::Expression, right_side : AST::Expression, into : Registers | Memory | Nil) : Type::Any #

Compile an assignement of any value to any other value. The left side of the assignement must be solvable to a memory location (a lvalue). The written value can also be written to another location (An assignement do have a type and an expression).


[View source]
def compile_assignment_or_binary(binary : AST::Binary, into : Registers | Memory | Nil) : Type::Any #

Compile a binary operator value, and move it's value if necessary.


[View source]
def compile_binary(binary : AST::Binary, into : Registers | Memory | Nil) : Type::Any #

[View source]
def compile_binary_to_call(binary : AST::Binary, into : Registers | Memory | Nil) : Type::Any #

[View source]
def compile_bitwise_and(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST) : Type::Any #

[View source]
def compile_bitwise_or(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST) : Type::Any #

[View source]
def compile_call(call : AST::Call, into : Registers | Memory | Nil) : Type::Any #

Compile a call. This cause all variable cached in registers to be stacked.


[View source]
def compile_comparator(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST, superior_to = false, or_equal = false) : Type::Any #

[View source]
def compile_equal(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST, neq = false) : Type::Any #

TODO Add long type equal ?

TODO Allow ptr comparison ?


[View source]
def compile_expression(expression : AST::Expression, into : Registers | Memory | Nil) : Type::Any #

Compile the value of any expression and move it's value if necessary.


[View source]
def compile_global(global : Unit::Global, into : Registers | Memory | Nil) : Type::Any #

Generate code necessary to move a global variable value in any location.


[View source]
def compile_global_lvalue(global : Unit::Global) : Memory #

Get the memory location of a global.


[View source]
def compile_identifier(identifier : AST::Identifier, into : Registers | Memory | Nil) : Type::Any #

Generate code necessary to move any value represened by an identifier in any location.


[View source]
def compile_identifier_lvalue(identifier : AST::Identifier) : Tuple(Memory, Type::Any) #

Get the memory location represented by an identifier.


[View source]
def compile_if(if_node : AST::If | AST::While, loop = false) #

Compile a if or while statement.


[View source]
def compile_literal(literal : AST::Literal, into : Registers | Memory | Nil) : Type::Any #

Generate code necessary to move a single-word literal value in any location.


[View source]
def compile_logic_and(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST) : Type::Any #

TODO Allow ptr comparison ?

FIXME should not compute right side if left side is falsy


[View source]
def compile_logic_or(left_side : Tuple(Registers, Type::Any), right_side : Tuple(Registers, Type::Any), into : Registers | Memory, node : AST) : Type::Any #

TODO Allow ptr comparison ?

FIXME should not compute right side if left side is truthy


[View source]
def compile_lvalue(expression : AST::Expression) : Tuple(Memory, Type::Any) | Nil #

Get the memory location represented by an expression. This is limited to global, variable, dereferenced pointer and access to them.

TODO Optimization when we do not need the Memory target and only care for side effect ?


[View source]
def compile_operator(operator : AST::Operator, into : Registers | Memory | Nil) : Type::Any #

Compile the value of any operation and move it's value if necessary.


[View source]
def compile_ptr_unary(operand : AST::Expression, into : Registers | Memory | Nil, node : AST) : Type::Any #

Compile dereferencement expression. It has it's own case instead of being in #compile_value_unary to simplify error display.


[View source]
def compile_return(ret : AST::Return) #

Compile the value of any expression and move it's value in the function return memory location. Move the stack back and jump to return address.


[View source]
def compile_sizeof(ast : AST::Sizeof, into : Registers | Memory | Nil) : Type::Any #

Generate code necessary to move a sizeof literal value in any location.


[View source]
def compile_statement(statement) #

Compile any statement.


[View source]
def compile_sugar_assignment(binary : AST::Binary, into : Registers | Memory | Nil) : Type::Any #

[View source]
def compile_table_access(binary : AST::Binary, into : Registers | Memory | Nil) : Type::Any #

[View source]
def compile_value_unary(unary : AST::Unary, into : Registers | Memory | Nil) : Type::Any #

Compile unary operator operating on word values.


[View source]
def compile_variable(variable : Variable, into : Registers | Memory | Nil) : Type::Any #

Generate code necessary to move a variable value in any location.


[View source]
def compile_variable_lvalue(variable : Variable) : Memory #

Get the memory location of a variable.


[View source]
def error(error, node = nil) #

[View source]
def extern : Bool #

Allow to skip compilation of prototypes only


[View source]
def grab_register(excludes = [] of Registers) #

Grab a register not excluded, free to use. If the grabbed register is used as a cache for a variable, or is holding a temporary value, the var is written to the stack so value is not lost.


[View source]
def is_free_to_use(register) #

[View source]
def jalr(a : Registers, b : Registers) #

[View source]
def lli(a : Registers, imm : Int32 | String | Memory) #

[View source]
def lui(a : Registers, imm : Int32 | String | Memory) #

[View source]
def lw(a : Registers, b : Registers, imm : Int32 | String | Memory) #

[View source]
def move(memory : Memory | Registers, constraint : Type::Any, into : Memory | Registers, force_to_memory = false) #

This is the big one. Move a bunch of memory from a source to a destination. It handle a wide range of case:

  • If the source value is in a register
  • If the source value is found at an address represented by an offset relative to an address in a register
  • If the source value is a variable that is cached in a register
  • If the source value is found at an address represented by an offset relative to a address in a variable
  • If the destination is a register
  • If the destination address is represented by an offset relative to an address in a register
  • If the destination address is represented by an offset relative to an address in a variable
  • If the destination is a variable that is or could be cached in a register All variable cache optimisation where a variable memory destination is not really written to thanks to caching within register can be disabled by setting force_to_memory to true. That should be usefull only when storing a var because it's cache register is needed for something else.

[View source]
def movi(a : Registers, imm : Int32 | String | Memory) #

[View source]
def name #

[View source]
def nand(a : Registers, b : Registers, c : Registers) #

[View source]
def predict_movi(a : Registers, imm : Int32 | String | Memory) #

Do nothing but predict the size a movi macro instruction will take in instructions


[View source]
def predict_size(&) #

[View source]

Allow to share the prototype of the function to external symbols.


[View source]
def store(variable) #

Ensure a variable value is written to ram and not in a cache so the register can be used for something else.


[View source]
def store_all #

Ensure all variables avlues are written to the ram, including temporaries.


[View source]
def sw(a : Registers, b : Registers, imm : Int32 | String | Memory) #

[View source]
def with_temporary(register : Registers, constraint : Type::Any, &) #

Run a computation step while ensuring a register value is kept or cached in stack. To be used with #uncache or #move.


[View source]