def make_il(self, il_code, symbol_table, c): """Make code for this node.""" lval = self.expr.lvalue(il_code, symbol_table, c) if not lval or not lval.modable(): err = "operand of {} operator not a modifiable lvalue" raise CompilerError(err.format(self.descrip), self.expr.r) val = self.expr.make_il(il_code, symbol_table, c) one = ILValue(val.ctype) if val.ctype.is_arith(): il_code.register_literal_var(one, 1) elif val.ctype.is_pointer() and val.ctype.arg.is_complete(): il_code.register_literal_var(one, val.ctype.arg.size) elif val.ctype.is_pointer() and not val.ctype.arg.is_complete(): err = "invalid arithmetic on pointer to incomplete type" raise CompilerError(err, self.op.r) else: err = "invalid type for {} operator" raise CompilerError(err.format(self.descrip), self.expr.r) new_val = ILValue(val.ctype) if self.return_new: il_code.add(self.cmd(new_val, val, one)) lval.set_to(new_val, il_code, self.expr.r) return new_val else: old_val = ILValue(val.ctype) il_code.add(value_cmds.Set(old_val, val)) il_code.add(self.cmd(new_val, val, one)) lval.set_to(new_val, il_code, self.expr.r) return old_val
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" lval = self.expr.lvalue(il_code, symbol_table, c) if not lval or not lval.modable(): err = f"operand of {self.descrip} operator not a modifiable lvalue" raise CompilerError(err, self.expr.r) val = self.expr.make_il(il_code, symbol_table, c) one = ILValue(val.ctype) if val.ctype.is_arith(): il_code.register_literal_var(one, 1) elif val.ctype.is_pointer() and val.ctype.arg.is_complete(): il_code.register_literal_var(one, val.ctype.arg.size) elif val.ctype.is_pointer(): # technically, this message is not quite right because for # non-object types, a type can be neither complete nor incomplete err = "invalid arithmetic on pointer to incomplete type" raise CompilerError(err, self.expr.r) else: err = f"invalid type for {self.descrip} operator" raise CompilerError(err, self.expr.r) new_val = ILValue(val.ctype) if self.return_new: il_code.add(self.cmd(new_val, val, one)) lval.set_to(new_val, il_code, self.expr.r) return new_val else: old_val = ILValue(val.ctype) il_code.add(value_cmds.Set(old_val, val)) il_code.add(self.cmd(new_val, val, one)) lval.set_to(new_val, il_code, self.expr.r) return old_val
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" expr = self.expr.make_il(il_code, symbol_table, c) if not expr.ctype.is_scalar(): err = "'!' operator requires scalar operand" raise CompilerError(err, self.r) # ILValue for storing the output out = ILValue(ctypes.integer) # ILValue for zero. zero = ILValue(ctypes.integer) il_code.register_literal_var(zero, "0") # ILValue for one. one = ILValue(ctypes.integer) il_code.register_literal_var(one, "1") # Label which skips the line which sets out to 0. end = il_code.get_label() il_code.add(value_cmds.Set(out, one)) il_code.add(control_cmds.JumpZero(expr, end)) il_code.add(value_cmds.Set(out, zero)) il_code.add(control_cmds.Label(end)) return out
def make_il(self, il_code, symbol_table, c): # ILValue for storing the output of this boolean operation out = ILValue(ctypes.integer) # ILValue for initial value of output variable. init = ILValue(ctypes.integer) il_code.register_literal_var(init, self.initial_value) # ILValue for other value of output variable. other = ILValue(ctypes.integer) il_code.register_literal_var(other, 1 - self.initial_value) # Label which immediately precedes the line which sets out to 0 or 1. set_out = il_code.get_label() # Label which skips the line which sets out to 0 or 1. end = il_code.get_label() err = "'{}' operator requires scalar operands".format(str(self.op)) left = self.left.make_il(il_code, symbol_table, c) if not left.ctype.is_scalar(): raise CompilerError(err, self.left.r) il_code.add(value_cmds.Set(out, init)) il_code.add(self.jump_cmd(left, set_out)) right = self.right.make_il(il_code, symbol_table, c) if not right.ctype.is_scalar(): raise CompilerError(err, self.right.r) il_code.add(self.jump_cmd(right, set_out)) il_code.add(control_cmds.Jump(end)) il_code.add(control_cmds.Label(set_out)) il_code.add(value_cmds.Set(out, other)) il_code.add(control_cmds.Label(end)) return out
def set_type(il_value, ctype, il_code, output=None): """If necessary, emit code to cast given il_value to the given ctype. If `output` is given, then this function expects output.ctype to be the same as ctype, sets `output` to the casted value, and returns output. If `output` is not given, this function returns an IL value with type ctype. If `il_value.ctype` matches given ctype, this function may return `il_value` directly. So, the return value should never have its value changed because this may affect the value in the given `il_value`. This function does no type checking and will never produce a warning or error. """ if not output and il_value.ctype.compatible(ctype): return il_value elif output == il_value: return il_value elif not output and il_value.literal: output = ILValue(ctype) if ctype.is_integral(): val = shift_into_range(il_value.literal.val, ctype) else: val = il_value.literal.val il_code.register_literal_var(output, val) return output else: if not output: output = ILValue(ctype) il_code.add(value_cmds.Set(output, il_value)) return output
def _lvalue(self, il_code, symbol_table, c): struct_addr = self.head.make_il(il_code, symbol_table, c) if not struct_addr.ctype.is_pointer(): err = "first argument of '->' must have pointer type" raise CompilerError(err, self.r) offset, ctype = self.get_offset_info(struct_addr.ctype.arg) shift = ILValue(ctypes.longint) il_code.register_literal_var(shift, str(offset)) out = ILValue(PointerCType(ctype)) il_code.add(math_cmds.Add(out, struct_addr, shift)) return IndirectLValue(out)
def get_size(ctype, num, il_code): """Return ILValue representing total size of `num` objects of given ctype. ctype - CType of object to count num - Integral ILValue representing number of these objects """ long_num = set_type(num, ctypes.longint, il_code) total = ILValue(ctypes.longint) size = ILValue(ctypes.longint) il_code.register_literal_var(size, str(ctype.size)) il_code.add(math_cmds.Mult(total, long_num, size)) return total
def val(self, il_code): self._fix_chunk_count(il_code) out = ILValue(self.ctype()) il_code.add( value_cmds.ReadRel(out, self.base, self.fixed_chunk, self.fixed_count)) return out
def addr(self, il_code): self._fix_chunk_count(il_code) out = ILValue(PointerCType(self.ctype())) il_code.add( value_cmds.AddrRel(out, self.base, self.fixed_chunk, self.fixed_count)) return out
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" left = self.left.make_il(il_code, symbol_table, c) right = self.right.make_il(il_code, symbol_table, c) if self._check_type(left, right): left, right = arith_convert(left, right, il_code) if left.literal and right.literal: # If NotImplementedError is raised, continue with execution. try: val = self._arith_const( shift_into_range(left.literal.val, left.ctype), shift_into_range(right.literal.val, right.ctype), left.ctype) out = ILValue(left.ctype) il_code.register_literal_var(out, val) return out except NotImplementedError: pass return self._arith(left, right, il_code) else: return self._nonarith(left, right, il_code)
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" # This is of function pointer type, so func.arg is the function type. func = self.func.make_il(il_code, symbol_table, c) if not func.ctype.is_pointer() or not func.ctype.arg.is_function(): descrip = "called object is not a function pointer" raise CompilerError(descrip, self.func.r) elif (func.ctype.arg.ret.is_incomplete() and not func.ctype.arg.ret.is_void()): # TODO: C11 spec says a function cannot return an array type, # but I can't determine how a function would ever be able to return # an array type. descrip = "function returns non-void incomplete type" raise CompilerError(descrip, self.func.r) if func.ctype.arg.no_info: final_args = self._get_args_without_prototype( il_code, symbol_table, c) else: final_args = self._get_args_with_prototype( func.ctype.arg, il_code, symbol_table, c) ret = ILValue(func.ctype.arg.ret) il_code.add(control_cmds.Call(func, final_args, ret)) return ret
def _nonarith(self, left, right, il_code): """Check equality of non-arithmetic expressions.""" # If either operand is a null pointer constant, cast it to the # other's pointer type. if left.ctype.is_pointer() and right.null_ptr_const: right = set_type(right, left.ctype, il_code) elif right.ctype.is_pointer() and left.null_ptr_const: left = set_type(left, right.ctype, il_code) # If both operands are not pointer types, quit now if not left.ctype.is_pointer() or not right.ctype.is_pointer(): with report_err(): err = "comparison between incomparable types" raise CompilerError(err, self.op.r) # If one side is pointer to void, cast the other to same. elif left.ctype.arg.is_void(): check_cast(right, left.ctype, self.op.r) right = set_type(right, left.ctype, il_code) elif right.ctype.arg.is_void(): check_cast(left, right.ctype, self.op.r) left = set_type(left, right.ctype, il_code) # If both types are still incompatible, warn! elif not left.ctype.compatible(right.ctype): with report_err(): err = "comparison between distinct pointer types" raise CompilerError(err, self.op.r) # Now, we can do comparison out = ILValue(ctypes.integer) il_code.add(self.eq_il_cmd(out, left, right)) return out
def _lvalue(self, il_code, symbol_table, c): head_lv = self.head.lvalue(il_code, symbol_table, c) struct_ctype = head_lv.ctype() if head_lv else None offset, ctype = self.get_offset_info(struct_ctype) if isinstance(head_lv, DirectLValue): head_val = self.head.make_il(il_code, symbol_table, c) return RelativeLValue(ctype, head_val, offset) else: struct_addr = head_lv.addr(il_code) shift = ILValue(ctypes.longint) il_code.register_literal_var(shift, str(offset)) out = ILValue(PointerCType(ctype)) il_code.add(math_cmds.Add(out, struct_addr, shift)) return IndirectLValue(out)
def make_il(self, il_code, symbol_table, c): """Make code for a literal number. This function does not actually make any code in the IL, it just returns a LiteralILValue that can be used in IL code by the caller. """ v = int(str(self.number)) if ctypes.int_min <= v <= ctypes.int_max: il_value = ILValue(ctypes.integer) elif ctypes.long_min <= v <= ctypes.long_max: il_value = ILValue(ctypes.longint) else: err = "integer literal too large to be represented by any " \ "integer type" raise CompilerError(err, self.number.r) il_code.register_literal_var(il_value, v) return il_value
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" right = self.right.make_il(il_code, symbol_table, c) lvalue = self.left.lvalue(il_code, symbol_table, c) if not lvalue or not lvalue.modable(): err = "expression on left of '{}' is not assignable" raise CompilerError(err.format(str(self.op)), self.left.r) if (lvalue.ctype().is_pointer() and right.ctype.is_integral() and self.accept_pointer): if not lvalue.ctype().arg.is_complete(): err = "invalid arithmetic on pointer to incomplete type" raise CompilerError(err, self.op.r) # Because of caching requirement of make_il and lvalue functions, # we know this call won't regenerate code for the left expression # beyond just what's needed to get the value stored at the lvalue. # This is important in cases like ``*func() += 10`` where func() # may have side effects if called twice. left = self.left.make_il(il_code, symbol_table, c) out = ILValue(left.ctype) shift = get_size(left.ctype.arg, right, il_code) il_code.add(self.command(out, left, shift)) lvalue.set_to(out, il_code, self.op.r) return out elif lvalue.ctype().is_arith() and right.ctype.is_arith(): left = self.left.make_il(il_code, symbol_table, c) out = ILValue(left.ctype) left, right = arith_convert(left, right, il_code) il_code.add(self.command(out, left, right)) lvalue.set_to(out, il_code, self.op.r) return out else: err = "invalid types for '{}' operator".format(str(self.op)) raise CompilerError(err, self.op.r)
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" # This node will have c.is_global set True, so we must change it to # for the children context. c = c.set_global(False) self.body.make_il(il_code, symbol_table, c) zero = ILValue(ctypes.integer) il_code.register_literal_var(zero, 0) il_code.add(control_cmds.Return(zero))
def _fix_chunk_count(self, il_code): """Convert chunk and count so that chunk is in {1, 2, 4, 8}. The Rel commands requre that chunk be in {1, 2, 4, 8}. If the given chunk value is not in this set, we multiply count and divide chunk by an appropriate value so that chunk is in {1, 2, 4, 8}, and then return the new value of chunk and the new value of count. In addition, this command moves `count` to a 64-bit value. """ # Cache the value of fixed_chunk and fixed_count so it is not # recomputed unnecessarily if self.fixed_chunk or self.fixed_count: return if not self.count: self.fixed_chunk, self.fixed_count = self.chunk, self.count return # TODO: Technically, if count is an unsigned long and `chunk` is in # `sizes`, we don't need to emit a SET command. resized_count = set_type(self.count, ctypes.longint, il_code) sizes = [8, 4, 2, 1] if self.chunk in sizes: self.fixed_chunk, self.fixed_count = self.chunk, resized_count return # Select the biggest legal size that divides given chunk size for new_chunk in sizes: if self.chunk % new_chunk == 0: break self.fixed_chunk = new_chunk scale = ILValue(ctypes.longint) scale_factor = str(int(self.chunk / new_chunk)) il_code.register_literal_var(scale, scale_factor) self.fixed_count = ILValue(ctypes.longint) il_code.add(math_cmds.Mult(self.fixed_count, resized_count, scale))
def _nonarith(self, left, right, il_code): """Compare non-arithmetic expressions.""" if not left.ctype.is_pointer() or not right.ctype.is_pointer(): err = "comparison between incomparable types" raise CompilerError(err, self.op.r) elif not left.ctype.compatible(right.ctype): err = "comparison between distinct pointer types" raise CompilerError(err, self.op.r) out = ILValue(ctypes.integer) il_code.add(self.comp_cmd(out, left, right)) return out
def _nonarith(self, left, right, il_code): """Make subtraction code if both operands are non-arithmetic type.""" # TODO: this isn't quite right when we allow qualifiers if (left.ctype.is_pointer() and right.ctype.is_pointer() and left.ctype.compatible(right.ctype)): if not (left.ctype.arg.is_complete() and right.ctype.arg.is_complete()): err = "invalid arithmetic on pointers to incomplete types" raise CompilerError(err, self.op.r) # Get raw difference in pointer values raw = ILValue(ctypes.longint) il_code.add(math_cmds.Subtr(raw, left, right)) # Divide by size of object out = ILValue(ctypes.longint) size = ILValue(ctypes.longint) il_code.register_literal_var(size, str(left.ctype.arg.size)) il_code.add(math_cmds.Div(out, raw, size)) return out # Left operand is pointer to complete object type, and right operand # is integer. elif left.ctype.is_pointer() and right.ctype.is_integral(): if not left.ctype.arg.is_complete(): err = "invalid arithmetic on pointer to incomplete type" raise CompilerError(err, self.op.r) out = ILValue(left.ctype) shift = get_size(left.ctype.arg, right, il_code) il_code.add(math_cmds.Subtr(out, left, shift)) return out else: descrip = "invalid operand types for subtraction" raise CompilerError(descrip, self.op.r)
def sizeof_ctype(self, ctype, range, il_code): """Raise CompilerError if ctype is not valid as sizeof argument.""" if ctype.is_function(): err = "sizeof argument cannot have function type" raise CompilerError(err, range) if ctype.is_incomplete(): err = "sizeof argument cannot have incomplete type" raise CompilerError(err, range) out = ILValue(ctypes.unsig_longint) il_code.register_literal_var(out, ctype.size) return out
def pointer_subsc(self, point, arith, il_code): """Return the LValue for this node. This function is called in the case where one operand is a pointer and the other operand is an integer. """ if not point.ctype.arg.is_complete(): err = "cannot subscript pointer to incomplete type" raise CompilerError(err, self.op.r) shift = get_size(point.ctype.arg, arith, il_code) out = ILValue(point.ctype) il_code.add(math_cmds.Add(out, point, shift)) return IndirectLValue(out)
def _arith(self, left, right, il_code): """Return the result of this operation on given arithmetic operands. Promotions and conversions are done by caller, so the implementation of this function need not convert operands. A default implementation is provided, but this can be overriden by derived classes. left - ILValue for left operand right - ILValue for right operand """ out = ILValue(left.ctype) il_code.add(self.default_il_cmd(out, left, right)) return out
def set_type(il_value, ctype, il_code, output=None): """If necessary, emit code to cast given il_value to the given ctype. This function does no type checking and will never produce a warning or error. """ if not output and il_value.ctype.compatible(ctype): return il_value elif output == il_value: return il_value else: if not output: output = ILValue(ctype) il_code.add(value_cmds.Set(output, il_value)) return output
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" expr = self.expr.make_il(il_code, symbol_table, c) if not self._check_type(expr): err = f"{self.descrip} requires {self.opnd_descrip} type operand" raise CompilerError(err, self.expr.r) # perform integer promotion if expr.ctype.size < 4: expr = set_type(expr, ctypes.integer, il_code) if self.cmd: out = ILValue(expr.ctype) # perform constant folding if expr.literal: val = self._arith_const(expr.literal.val, expr.ctype) val = shift_into_range(val, expr.ctype) il_code.register_literal_var(out, val) else: il_code.add(self.cmd(out, expr)) return out return expr
def make_il(self, il_code, symbol_table, c): """Make code for this node.""" # This is of function pointer type, so func.arg is the function type. func = self.func.make_il(il_code, symbol_table, c) if not func.ctype.is_pointer() or not func.ctype.arg.is_function(): descrip = "called object is not a function pointer" raise CompilerError(descrip, self.func.r) if not func.ctype.arg.args: final_args = self._get_args_without_prototype( il_code, symbol_table, c) else: final_args = self._get_args_with_prototype( func.ctype.arg, il_code, symbol_table, c) ret = ILValue(func.ctype.arg.ret) il_code.add(control_cmds.Call(func, final_args, ret)) return ret
def _nonarith(self, left, right, il_code): """Make addition code if either operand is non-arithmetic type.""" # One operand should be pointer to complete object type, and the # other should be any integer type. if left.ctype.is_pointer() and right.ctype.is_integral(): arith, pointer = right, left elif right.ctype.is_pointer() and left.ctype.is_integral(): arith, pointer = left, right else: err = "invalid operand types for addition" raise CompilerError(err, self.op.r) if not pointer.ctype.arg.is_complete(): err = "invalid arithmetic on pointer to incomplete type" raise CompilerError(err, self.op.r) # Multiply by size of objects out = ILValue(pointer.ctype) shift = get_size(pointer.ctype.arg, arith, il_code) il_code.add(math_cmds.Add(out, pointer, shift)) return out
def do_body(self, il_code, symbol_table, c): """Create code for function body. Caller must check that this function has a body. """ is_main = self.identifier.content == "main" for param in self.param_names: if not param: err = "function definition missing parameter name" raise CompilerError(err, self.range) if is_main: self.check_main_type() c = c.set_return(self.ctype.ret) il_code.start_func(self.identifier.content) symbol_table.new_scope() num_params = len(self.ctype.args) iter = zip(self.ctype.args, self.param_names, range(num_params)) for ctype, param, i in iter: arg = symbol_table.add_variable( param, ctype, symbol_table.DEFINED, None, symbol_table.AUTOMATIC) il_code.add(value_cmds.LoadArg(arg, i)) self.body.make_il(il_code, symbol_table, c, no_scope=True) if not il_code.always_returns() and is_main: zero = ILValue(ctypes.integer) il_code.register_literal_var(zero, 0) il_code.add(control_cmds.Return(zero)) elif not il_code.always_returns(): il_code.add(control_cmds.Return(None)) symbol_table.end_scope()
def _lvalue(self, il_code, symbol_table, c): il_value = ILValue(ArrayCType(ctypes.char, len(self.chars))) il_code.register_string_literal(il_value, self.chars) return DirectLValue(il_value)
def _arith(self, left, right, il_code): """Compare arithmetic expressions.""" out = ILValue(ctypes.integer) il_code.add(self.comp_cmd(out, left, right)) return out
def _arith(self, left, right, il_code): """Check equality of arithmetic expressions.""" out = ILValue(ctypes.integer) il_code.add(self.eq_il_cmd(out, left, right)) return out