├── LICENSE ├── README.mkd ├── rvcc.py └── test_rvcc.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 lowRISC CIC 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # riscv-calling-conv-model 2 | 3 | This project was created with the aim of providing a "golden model" of the 4 | [RISC-V calling 5 | convention](https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md). 6 | It will soon be expanded to support test case generation, to form part of a 7 | RISC-V ABI compliance suite. 8 | 9 | ## Usage 10 | 11 | Usage example: 12 | 13 | $ python3 14 | Python 3.6.2 (default, Jul 20 2017, 03:52:27) 15 | [GCC 7.1.1 20170630] on linux 16 | Type "help", "copyright", "credits" or "license" for more information. 17 | >>> from rvcc import * 18 | >>> m = RVMachine(xlen=32, flen=64) 19 | >>> m.call([ 20 | ... Int32, 21 | ... Double, 22 | ... Struct(Int8, Array(Float, 1)), 23 | ... Struct(Array(Int8, 20)), 24 | ... Int64, 25 | ... Int64, 26 | ... Int64]) 27 | Args: 28 | arg00: SInt32 29 | arg01: FP64 30 | arg02: Struct([SInt8, Pad24, Array(FP32*1, s32, a32)], s64, a32) 31 | arg03: Struct([Array(SInt8*20, s160, a8)], s160, a8) 32 | arg04: SInt64 33 | arg05: SInt64 34 | arg06: SInt64 35 | 36 | GPRs: 37 | GPR[a0]: arg00 38 | GPR[a1]: arg02[0:7] 39 | GPR[a2]: &arg03 40 | GPR[a3]: arg04[0:31] 41 | GPR[a4]: arg04[32:63] 42 | GPR[a5]: arg05[0:31] 43 | GPR[a6]: arg05[32:63] 44 | GPR[a7]: arg06[0:31] 45 | 46 | FPRs: 47 | FPR[fa0]: arg01 48 | FPR[fa1]: arg02[32:63] 49 | FPR[fa2]: ? 50 | FPR[fa3]: ? 51 | FPR[fa4]: ? 52 | FPR[fa5]: ? 53 | FPR[fa6]: ? 54 | FPR[fa7]: ? 55 | 56 | Stack: 57 | arg06[32:63] (oldsp+0) 58 | 59 | ## License 60 | 61 | Copyright (c) 2017 lowRISC CIC 62 | 63 | Permission is hereby granted, free of charge, to any person 64 | obtaining a copy of this software and associated documentation 65 | files (the "Software"), to deal in the Software without 66 | restriction, including without limitation the rights to use, 67 | copy, modify, merge, publish, distribute, sublicense, and/or sell 68 | copies of the Software, and to permit persons to whom the 69 | Software is furnished to do so, subject to the following 70 | conditions: 71 | 72 | The above copyright notice and this permission notice shall be 73 | included in all copies or substantial portions of the Software. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 76 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 77 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 78 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 79 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 80 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 81 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 82 | OTHER DEALINGS IN THE SOFTWARE. 83 | 84 | -------------------------------------------------------------------------------- /rvcc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Part of riscv-calling-conv-model 3 | # https://github.com/lowRISC/riscv-calling-conv-model 4 | # 5 | # See LICENSE file for copyright and license details 6 | 7 | import copy, operator, random 8 | 9 | # All alignments and sizes are currently specified in bits 10 | 11 | def align_to(x, align): 12 | return x - (x % -align) 13 | 14 | class Int(object): 15 | def __init__(self, size, signed=True): 16 | self.size = size 17 | self.alignment = size 18 | self.signed = signed 19 | def __repr__(self): 20 | return '{}Int{}'.format('S' if self.signed else 'U', self.size) 21 | def ctype(self): 22 | ty = 'int'+str(self.size)+'_t' 23 | if not self.signed: 24 | return 'u'+ty 25 | else: 26 | return ty 27 | def random_literal(self): 28 | if self.signed: 29 | lower = -2**(self.size-1) 30 | upper = (2**(self.size-1))-1 31 | val = random.randint(lower, upper) 32 | else: 33 | val = random.randint(0, 2**(self.size-1)) 34 | suffix = '' 35 | if not self.signed: 36 | suffix += 'u' 37 | if self.size == 64: 38 | suffix += 'll' 39 | elif self.size > 64: 40 | raise ValueError("Can't create literal of that size") 41 | return str(val) + suffix 42 | 43 | def UInt(size): 44 | return Int(size, signed=False) 45 | 46 | def SInt(size): 47 | return Int(size, signed=True) 48 | 49 | Int8, Int16, Int32, Int64, Int128 = [SInt(i) for i in [8,16,32,64,128]] 50 | SInt8, SInt16, SInt32, SInt64, SInt128 = [Int8, Int16, Int32, Int64, Int128] 51 | UInt8, UInt16, UInt32, UInt64, UInt128 = [UInt(i) for i in [8,16,32,64,128]] 52 | Char = UInt8 53 | 54 | class FP(object): 55 | def __init__(self, size): 56 | self.size = size 57 | self.alignment = size 58 | def __repr__(self): 59 | return 'FP{}'.format(self.size) 60 | def ctype(self): 61 | if self.size == 32: 62 | return 'float' 63 | elif self.size == 64: 64 | return 'double' 65 | elif self.size == 128: 66 | return 'long double' 67 | else: 68 | raise ValueError('no ctype') 69 | def random_literal(self): 70 | # For now, don't bother generating any possible fp value 71 | int_part = random.randint(-1000, 1000) 72 | fract_part = random.randint(0, 9) 73 | if self.size == 32: 74 | suffix = 'f' 75 | elif self.size == 64: 76 | suffix = '' 77 | elif self.size == 128: 78 | suffix = 'l' 79 | else: 80 | raise ValueError("Can't represent fp value of that size") 81 | return str(int_part) + '.' + str(fract_part) + suffix 82 | 83 | Float, Double, LongDouble = FP(32), FP(64), FP(128) 84 | FP32, FP64, FP128 = Float, Double, LongDouble 85 | 86 | class Ptr(object): 87 | def __init__(self, size): 88 | self.size = size 89 | self.alignment = size 90 | def __repr__(self): 91 | return 'Ptr{}'.format(self.size) 92 | def ctype(self): 93 | return 'char*' 94 | def random_literal(self): 95 | val = random.randint(0, 2**(self.size-1)) 96 | suffix = 'u' 97 | if self.size == 64: 98 | suffix += 'll' 99 | return '(char*)' + hex(val) + suffix 100 | 101 | Ptr32, Ptr64 = Ptr(32), Ptr(64) 102 | 103 | class Pad(object): 104 | def __init__(self, size): 105 | self.size = size 106 | self.alignment = 1 107 | def __repr__(self): 108 | return 'Pad{}'.format(self.size) 109 | 110 | field_names = ['fld'+str(i) for i in range(0, 100)] 111 | struct_counter = 0 112 | 113 | class Struct(object): 114 | # Add padding objects when necessary to ensure struct members have their 115 | # desired alignment 116 | def add_padding(self): 117 | i = 0 118 | cur_offset = 0 119 | while i < len(self.members): 120 | wanted_align = self.members[i].alignment 121 | if (cur_offset % wanted_align) != 0: 122 | pad_size = -(cur_offset % -wanted_align) 123 | self.members.insert(i, Pad(pad_size)) 124 | i += 1 125 | cur_offset += pad_size 126 | cur_offset += self.members[i].size 127 | i+= 1 128 | 129 | def __init__(self, *members): 130 | global struct_counter 131 | self.members = list(members) 132 | if len(members) == 0: 133 | self.alignment = 8 134 | self.size = 0 135 | return 136 | self.add_padding() 137 | self.alignment = max(m.alignment for m in members) 138 | self.size = sum(m.size for m in members) 139 | self.size = align_to(self.size, self.alignment) 140 | self.name = 'strctty'+str(struct_counter) 141 | struct_counter += 1 142 | 143 | def flatten(self): 144 | children = [] 145 | for ty in self.members: 146 | if hasattr(ty, 'flatten'): 147 | children += ty.flatten() 148 | else: 149 | children.append(ty) 150 | return children 151 | 152 | def __repr__(self): 153 | return 'Struct({}, s{}, a{})'.format(self.members, 154 | self.size, self.alignment) 155 | 156 | def cdecl(self): 157 | res = 'struct ' + self.name + ' { ' 158 | mem_ctypes = [] 159 | i = 0 160 | for ty in self.members: 161 | if hasattr(ty, 'flatten'): 162 | raise ValueError("don't support nested aggregates") 163 | if isinstance(ty, Pad): 164 | continue 165 | mem_ctypes.append(ty.ctype() + ' ' + field_names[i] + ';') 166 | i += 1 167 | return res + ' '.join(mem_ctypes) + ' }' 168 | 169 | def ctype(self): 170 | return 'struct ' + self.name 171 | 172 | def random_literal(self): 173 | res = '(struct ' + self.name + '){' 174 | random_lits = [] 175 | for ty in self.members: 176 | if hasattr(ty, 'flatten'): 177 | raise ValueError("don't support nested aggregates") 178 | if isinstance(ty, Pad): 179 | continue 180 | random_lits.append(ty.random_literal()) 181 | return res + ', '.join(random_lits) + '}' 182 | 183 | class Union(object): 184 | def __init__(self, *members): 185 | self.members = list(members) 186 | self.alignment = max(m.alignment for m in members) 187 | self.size = max(m.size for m in members) 188 | self.size = align_to(self.size, self.alignment) 189 | def __repr__(self): 190 | return 'Union({}, s{}, a{})'.format(self.members, 191 | self.size, self.alignment) 192 | 193 | class Array(object): 194 | def __init__(self, ty, num_elements): 195 | self.ty = ty 196 | self.num_elements = num_elements 197 | self.alignment = ty.alignment 198 | self.size = ty.size * num_elements 199 | 200 | def flatten(self): 201 | if hasattr(self.ty, 'flatten'): 202 | return self.ty.flatten() * self.num_elements 203 | else: 204 | return [self.ty] * self.num_elements 205 | 206 | def __repr__(self): 207 | return 'Array({}*{}, s{}, a{})'.format(self.ty, 208 | self.num_elements, self.size, self.alignment) 209 | 210 | class Slice(object): 211 | def __init__(self, child, low, high): 212 | self.child = child 213 | self.low = low 214 | self.high = high 215 | self.size = high - low + 1 216 | self.alignment = self.size 217 | def __repr__(self): 218 | return '{}[{}:{}]'.format(self.child, self.low, self.high) 219 | 220 | class VarArgs(object): 221 | def __init__(self, *args): 222 | self.args = list(args) 223 | def __repr__(self): 224 | return 'VarArgs({})'.format(self.args) 225 | 226 | class CCState(object): 227 | def __init__(self, xlen, flen, in_args, var_args_index, out_arg): 228 | self.xlen = xlen 229 | self.flen = flen 230 | self.gprs_left = 8 231 | self.gprs = [None] * 32 232 | self.fprs = None 233 | self.fprs_left = None 234 | if flen: 235 | self.fprs = [None] * 32 236 | self.fprs_left = 8 237 | self.stack = [] 238 | self.type_name_mapping = {} 239 | self.in_args = in_args 240 | self.var_args_index = var_args_index 241 | self.out_arg = out_arg 242 | self.name_types(in_args, var_args_index, out_arg) 243 | 244 | def name_types(self, in_args, var_args_index, out_arg): 245 | i = 0 246 | arg_idx = 0 247 | varg_idx = 0 248 | for index, ty in enumerate(in_args): 249 | if index >= var_args_index: 250 | self.type_name_mapping[ty] = 'varg'+str(varg_idx).zfill(2) 251 | varg_idx += 1 252 | else: 253 | self.type_name_mapping[ty] = 'arg'+str(arg_idx).zfill(2) 254 | arg_idx += 1 255 | if out_arg: 256 | self.type_name_mapping[out_arg] = 'ret' 257 | 258 | def next_arg_gpr(self): 259 | return (8-self.gprs_left)+10 260 | 261 | def skip_gpr(self): 262 | if (self.gprs_left == 0): 263 | raise ValueError('all GPRs assigned') 264 | self.gprs_left -= 1 265 | 266 | def assign_to_gpr_or_stack(self, ty): 267 | if ty.size > self.xlen: 268 | raise ValueError('object is larger than xlen') 269 | if self.gprs_left >= 1: 270 | self.assign_to_gpr(ty) 271 | else: 272 | self.assign_to_stack(ty) 273 | 274 | def assign_to_gpr(self, ty): 275 | if ty.size > self.xlen: 276 | raise ValueError('object is larger than xlen') 277 | if self.gprs_left <= 0: 278 | raise ValueError('all argument registers already assigned') 279 | self.gprs[self.next_arg_gpr()] = ty 280 | self.gprs_left -= 1 281 | 282 | def next_arg_fpr(self): 283 | return (8-self.fprs_left)+10 284 | 285 | def assign_to_fpr(self, ty): 286 | if ty.size > self.flen: 287 | raise ValueError('object is larger than flen') 288 | if self.fprs_left <= 0: 289 | raise ValueError('all FP argument registers already assigned') 290 | self.fprs[self.next_arg_fpr()] = ty 291 | self.fprs_left -= 1 292 | 293 | def assign_to_stack(self, ty): 294 | if ty.size > 2*self.xlen: 295 | raise ValueError('objects larger than 2x xlen should be passed by reference') 296 | self.stack.append(ty) 297 | 298 | def pass_by_reference(self, ty): 299 | ptrty = Ptr(self.xlen) 300 | self.assign_to_gpr_or_stack(ptrty) 301 | if ty in self.type_name_mapping: 302 | self.type_name_mapping[ptrty] = '&'+self.type_name_mapping[ty] 303 | 304 | def typestr_or_name(self, ty): 305 | suffix = '' 306 | if ty == None: 307 | return '?' 308 | elif isinstance(ty, Slice): 309 | suffix = '[{}:{}]'.format(ty.low, ty.high) 310 | if ty.child in self.type_name_mapping: 311 | return self.type_name_mapping[ty.child]+suffix 312 | else: 313 | return repr(ty) 314 | return self.type_name_mapping.get(ty, repr(ty)) 315 | 316 | def get_oldsp_rel_stack_locs(self): 317 | locs = [] 318 | oldsp_off = 0 319 | for idx, ty in enumerate(self.stack): 320 | if idx == 0: 321 | locs.append(0) 322 | continue 323 | obj = self.stack[idx] 324 | prev_obj = self.stack[idx-1] 325 | oldsp_off += prev_obj.size 326 | oldsp_off = align_to(oldsp_off, self.xlen) 327 | oldsp_off = align_to(oldsp_off, obj.alignment) 328 | locs.append(oldsp_off//8) 329 | return locs 330 | 331 | def get_oldsp_rel_stack_loc(self, obj_idx): 332 | if obj_idx < 0 or obj_idx >= len(self.stack): 333 | raise ValueError("invalid stack object") 334 | obj = self.stack[obj_idx] 335 | sp_offset = 0 336 | for i in range(0, obj_idx): 337 | sp_offset = align_to(sp_offset, max(self.xlen, self.stack[i].alignment)) 338 | sp_offset += self.stack[i].size 339 | sp_offset = align_to(sp_offset, max(self.xlen, obj.alignment)) 340 | return sp_offset//8 341 | 342 | def __repr__(self): 343 | out = [] 344 | if len(self.type_name_mapping) > 0: 345 | out.append('Args:') 346 | for item in sorted(self.type_name_mapping.items(), 347 | key=operator.itemgetter(1)): 348 | if item[1][0] == '&': 349 | continue 350 | out.append('{}: {}'.format(item[1], item[0])) 351 | out.append('') 352 | out.append('GPRs:') 353 | for i in range(0, 8): 354 | out.append('GPR[a{}]: {}'.format(i, 355 | self.typestr_or_name(self.gprs[i+10]))) 356 | 357 | if self.flen: 358 | out.append('\nFPRs:') 359 | for i in range(0, 8): 360 | out.append('FPR[fa{}]: {}'.format(i, 361 | self.typestr_or_name(self.fprs[i+10]))) 362 | 363 | out.append('\nStack:') 364 | oldsp_offs = self.get_oldsp_rel_stack_locs() 365 | for idx, ty in enumerate(self.stack): 366 | out.append('{} (oldsp+{})'.format(self.typestr_or_name(ty), 367 | oldsp_offs[idx])) 368 | return '\n'.join(out) 369 | 370 | class InvalidVarArgs(Exception): 371 | pass 372 | 373 | class RVMachine(object): 374 | def __init__(self, xlen=64, flen=None): 375 | if xlen not in [32, 64, 128]: 376 | raise ValueError("unsupported XLEN") 377 | if flen and flen not in [32, 64, 128]: 378 | raise ValueError("unsupported FLEN") 379 | self.xlen = xlen 380 | self.flen = flen 381 | 382 | def ptr_ty(self): 383 | return Ptr(self.xlen) 384 | 385 | # Should be called after any expected VarArgs has been flattened 386 | def verify_arg_list(self, in_args, out_arg): 387 | # Ensure all argument/return type objects are unique 388 | if (len(in_args) != len(set(in_args))) or out_arg in in_args: 389 | raise ValueError("Unique type objects must be used") 390 | if isinstance(out_arg, VarArgs): 391 | raise InvalidVarArgs("Return type cannot be varargs") 392 | for arg in in_args: 393 | if (isinstance(arg, VarArgs)): 394 | raise InvalidVarArgs("VarArgs must be last element") 395 | 396 | def ret(self, ty): 397 | # Values are returned in the same way a named argument of the same 398 | # type would be passed. If it would be passed by reference, the 399 | # argument list is rewritten so the first argument is a pointer to 400 | # caller-allocated memory where the return value can be placed. 401 | in_args = [] 402 | if ty: 403 | in_args = [ty] 404 | 405 | state = self.call(in_args) 406 | 407 | # Detect the case where the return value would be passed by reference 408 | if state.typestr_or_name(state.gprs[10]).startswith('&'): 409 | state.gprs[10] = None 410 | 411 | newty = next(iter(state.type_name_mapping)) 412 | state.type_name_mapping[newty] = 'ret' 413 | return state 414 | 415 | 416 | def call(self, in_args, out_arg=None): 417 | # Remove the VarArgs wrapper type, but keep track of the arguments 418 | # specified to be vararg. var_args_index will point past the end of 419 | # in_args if there are no varargs. 420 | var_args_index = len(in_args) 421 | if len(in_args) >= 1 and isinstance(in_args[-1], VarArgs): 422 | var_args = in_args[-1].args 423 | in_args.pop() 424 | var_args_index = len(in_args) 425 | in_args.extend(var_args) 426 | 427 | # Ensure there's a unique object to represent every argument type 428 | in_args = [copy.copy(arg) for arg in in_args] 429 | out_arg = copy.copy(out_arg) 430 | 431 | self.verify_arg_list(in_args, out_arg) 432 | 433 | # Filter out empty structs 434 | in_args = [arg for arg in in_args if arg.size > 0] 435 | 436 | def isStruct(ty): 437 | return isinstance(ty, Struct) 438 | def isArray(ty): 439 | return isinstance(ty, Array) 440 | def isFP(ty): 441 | return isinstance(ty, FP) 442 | def isInt(ty): 443 | return isinstance(ty, Int) 444 | def isPad(ty): 445 | return isinstance(ty, Pad) 446 | 447 | xlen, flen = self.xlen, self.flen 448 | 449 | # Promote varargs 450 | for idx in range(var_args_index, len(in_args)): 451 | arg = in_args[idx] 452 | if isInt(arg) and arg.size < xlen: 453 | arg.size = xlen 454 | arg.alignment = xlen 455 | elif isFP(arg) and arg.size < xlen: 456 | arg.size = flen 457 | arg.alignment = flen 458 | 459 | state = CCState(xlen, flen, in_args, var_args_index, out_arg) 460 | 461 | # Error out if Arrays are being passed/returned directly. This isn't 462 | # supported in C 463 | if isArray(out_arg) or any(isArray(ty) for ty in in_args): 464 | raise ValueError('Byval arrays not supported in C') 465 | 466 | # Catch the special case of returning a struct that can be returned 467 | # according to the floating point calling convention 468 | if flen and isStruct(out_arg) and out_arg.size <= 2*flen: 469 | ty = Struct(*out_arg.flatten()) 470 | mems = [mem for mem in ty.members if not isPad(mem)] 471 | if len(mems) == 2: 472 | ty1, ty2 = mems[0], mems[1] 473 | if ((isFP(ty1) and isFP(ty2) and 474 | ty1.size <= flen and ty2.size <= flen) or 475 | (isFP(ty1) and isInt(ty2) and 476 | ty1.size <= flen and ty2.size <= xlen) or 477 | (isInt(ty1) and isFP(ty2) and 478 | ty1.size <= xlen and ty2.size <= flen)): 479 | pass 480 | else: 481 | state.pass_by_reference(out_arg) 482 | # If the return value won't be returned in registers, the address to 483 | # store it to is passed as an implicit first parameter 484 | elif out_arg and out_arg.size > 2*xlen: 485 | state.pass_by_reference(out_arg) 486 | 487 | for index, ty in enumerate(in_args): 488 | is_var_arg = index >= var_args_index 489 | # Special-case rules introduced by the floating point calling 490 | # convention 491 | if flen and not is_var_arg: 492 | # Flatten the struct if there is any chance it may be passed 493 | # in fprs/gprs (i.e. it is possible it contains two floating 494 | # point values, or one fp + one int) 495 | flat_ty = ty 496 | if isStruct(ty) and ty.size <= max(2*flen, 2*xlen): 497 | flat_ty = Struct(*ty.flatten()) 498 | if len(flat_ty.members) == 1: 499 | flat_ty = flat_ty[0] 500 | if isFP(flat_ty) and flat_ty.size <= flen and state.fprs_left >= 1: 501 | state.assign_to_fpr(ty) 502 | continue 503 | elif isStruct(flat_ty) and flat_ty.size <= 2*flen: 504 | # Ignore any padding 505 | mems = [mem for mem in flat_ty.members if not isPad(mem)] 506 | if len(mems) == 2: 507 | ty1, ty2 = mems[0], mems[1] 508 | ty1_slice = Slice(ty, 0, ty1.size - 1) 509 | ty2_off = max(ty1.size, ty2.alignment) 510 | ty2_slice = Slice(ty, ty2_off, 511 | ty2_off + ty2.size - 1) 512 | if (isFP(ty1) and isFP(ty2) 513 | and ty1.size <= flen and ty2.size <= flen 514 | and state.fprs_left >= 2): 515 | state.assign_to_fpr(ty1_slice) 516 | state.assign_to_fpr(ty2_slice) 517 | continue 518 | elif (isFP(ty1) and isInt(ty2) and 519 | ty1.size <= flen and ty2.size <= xlen and 520 | state.fprs_left >= 1 and state.gprs_left >= 1): 521 | state.assign_to_fpr(ty1_slice) 522 | state.assign_to_gpr(ty2_slice) 523 | continue 524 | elif (isInt(ty1) and isFP(ty2) and 525 | ty1.size <= xlen and ty2.size <= flen and 526 | state.gprs_left >=1 and state.fprs_left >=1): 527 | state.assign_to_gpr(ty1_slice) 528 | state.assign_to_fpr(ty2_slice) 529 | continue 530 | 531 | # If we got to here, the standard integer calling convention 532 | # applies 533 | if ty.size <= xlen: 534 | state.assign_to_gpr_or_stack(ty) 535 | elif ty.size <= 2*xlen: 536 | # 2xlen-aligned varargs must be passed in an aligned register 537 | # pair 538 | if (is_var_arg and ty.alignment == 2*xlen 539 | and state.gprs_left % 2 == 1): 540 | state.skip_gpr() 541 | if state.gprs_left > 0: 542 | state.assign_to_gpr_or_stack(Slice(ty, 0, xlen-1)) 543 | state.assign_to_gpr_or_stack(Slice(ty, xlen, 2*xlen - 1)) 544 | else: 545 | state.assign_to_stack(ty) 546 | else: 547 | state.pass_by_reference(ty) 548 | return state 549 | 550 | if __name__ == '__main__': 551 | print(""" 552 | Usage example: 553 | $ python3 554 | Python 3.6.2 (default, Jul 20 2017, 03:52:27) 555 | [GCC 7.1.1 20170630] on linux 556 | Type "help", "copyright", "credits" or "license" for more information. 557 | >>> from rvcc import * 558 | >>> m = RVMachine(xlen=32, flen=64) 559 | >>> m.call([ 560 | ... Int32, 561 | ... Double, 562 | ... Struct(Int8, Array(Float, 1)), 563 | ... Struct(Array(Int8, 20)), 564 | ... Int64, 565 | ... Int64, 566 | ... Int64]) 567 | Args: 568 | arg00: SInt32 569 | arg01: FP64 570 | arg02: Struct([SInt8, Pad24, Array(FP32*1, s32, a32)], s64, a32) 571 | arg03: Struct([Array(SInt8*20, s160, a8)], s160, a8) 572 | arg04: SInt64 573 | arg05: SInt64 574 | arg06: SInt64 575 | 576 | GPRs: 577 | GPR[a0]: arg00 578 | GPR[a1]: arg02[0:7] 579 | GPR[a2]: &arg03 580 | GPR[a3]: arg04[0:31] 581 | GPR[a4]: arg04[32:63] 582 | GPR[a5]: arg05[0:31] 583 | GPR[a6]: arg05[32:63] 584 | GPR[a7]: arg06[0:31] 585 | 586 | FPRs: 587 | FPR[fa0]: arg01 588 | FPR[fa1]: arg02[32:63] 589 | FPR[fa2]: ? 590 | FPR[fa3]: ? 591 | FPR[fa4]: ? 592 | FPR[fa5]: ? 593 | FPR[fa6]: ? 594 | FPR[fa7]: ? 595 | 596 | Stack: 597 | arg06[32:63] (oldsp+0) 598 | """) 599 | -------------------------------------------------------------------------------- /test_rvcc.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from rvcc import * 3 | 4 | def test_first_class_array_arg(): 5 | with pytest.raises(ValueError): 6 | RVMachine().call([Array(Int8, 3)]) 7 | 8 | def test_first_class_array_ret(): 9 | with pytest.raises(ValueError): 10 | RVMachine().call([], Array(Int8, 2)) 11 | 12 | def test_invalid_xlen(): 13 | with pytest.raises(ValueError): 14 | RVMachine(xlen=16) 15 | with pytest.raises(ValueError): 16 | RVMachine(xlen=33) 17 | with pytest.raises(ValueError): 18 | RVMachine(xlen=256) 19 | 20 | def test_invalid_flen(): 21 | with pytest.raises(ValueError): 22 | RVMachine(flen=16) 23 | with pytest.raises(ValueError): 24 | RVMachine(flen=33) 25 | with pytest.raises(ValueError): 26 | RVMachine(flen=256) 27 | 28 | def get_arg_gprs(state): 29 | return [state.typestr_or_name(state.gprs[idx]) for idx in range(10, 18)] 30 | 31 | def get_arg_fprs(state): 32 | return [state.typestr_or_name(state.fprs[idx]) for idx in range(10, 18)] 33 | 34 | def get_stack_objects(state): 35 | return [state.typestr_or_name(obj) for obj in state.stack] 36 | 37 | def test_no_args_void_return(): 38 | m = RVMachine(xlen=32) 39 | state = m.call([]) 40 | assert(get_arg_gprs(state)[0:1] == ["?"]) 41 | 42 | def test_many_args(): 43 | # The stack should be used when arg registers are exhausted 44 | m = RVMachine(xlen=32) 45 | state = m.call([UInt8, Char, Char, Char, Char, Char, Char, 46 | Int8, Int8, Int8, UInt128]) 47 | assert(get_stack_objects(state) == ["arg08", "arg09", "&arg10"]) 48 | assert(state.get_oldsp_rel_stack_locs() == [0, 4, 8]) 49 | 50 | def test_2xlen_rv32i(): 51 | # 2xlen arguments are passed in GPRs, which need not be 'aligned' register 52 | # pairs 53 | m = RVMachine(xlen=32) 54 | state = m.call([Int64, Int32, Double, Struct(Int8, Int32, Int8)]) 55 | assert(get_arg_gprs(state)[0:8] == ["arg00[0:31]", "arg00[32:63]", 56 | "arg01", "arg02[0:31]", "arg02[32:63]", "arg03[0:31]", "arg03[32:63]", "?"]) 57 | 58 | # If only one arg GPR is available, the other half goes on the stack 59 | state = m.call([Int8, Int8, Int8, Int8, Int8, Int8, Int8, 60 | Double]) 61 | assert(get_arg_gprs(state)[6:8] == ["arg06", "arg07[0:31]"]) 62 | assert(len(state.stack) == 1) 63 | assert(state.typestr_or_name(state.stack[0]) == "arg07[32:63]") 64 | 65 | # 2xlen arguments must have their alignment maintained when passed on the 66 | # stack 67 | state = m.call([Int8, Int8, Int8, Int8, Int8, Int8, Int8, 68 | Int8, Int8, Double]) 69 | assert(get_stack_objects(state) == ["arg08", "arg09"]) 70 | assert(state.get_oldsp_rel_stack_locs() == [0, 8]) 71 | 72 | def test_gt_2xlen_rv32i(): 73 | # scalars and aggregates > 2xlen are passed indirect 74 | m = RVMachine(xlen=32) 75 | state = m.call([Int128, LongDouble, Struct(Int64, Double)]) 76 | assert(get_arg_gprs(state)[0:4] == ["&arg00", "&arg01", "&arg02", "?"]) 77 | 78 | def test_fp_scalars_rv32ifd(): 79 | m = RVMachine(xlen=32, flen=64) 80 | # FPRs should be used as well as GPRs 81 | state = m.call([Float, Int64, Double, Int32]) 82 | assert(get_arg_gprs(state)[0:4] == ["arg01[0:31]", "arg01[32:63]", 83 | "arg03", "?"]) 84 | assert(get_arg_fprs(state)[0:3] == ["arg00", "arg02", "?"]) 85 | 86 | # Use GPRs when FPR arg registers are exhausted 87 | state = m.call([Float, Double, Float, Double, Float, Double, Float, 88 | Double, Char, Double, Float]) 89 | assert(get_arg_gprs(state)[0:5] == ["arg08", "arg09[0:31]", 90 | "arg09[32:63]", "arg10", "?"]) 91 | 92 | # A float might end up split between stack and GPRs due to the FPRs being 93 | # exhausted 94 | state = m.call([Float, Int64, Double, Int64, Float, Int64, Double, Char, 95 | Float, Double, Float, Double, Double]) 96 | assert(get_arg_gprs(state)[6:8] == ["arg07", "arg12[0:31]"]) 97 | assert(get_stack_objects(state) == ["arg12[32:63]"]) 98 | 99 | # Greater than flen, pass according to integer calling convention 100 | state = m.call([LongDouble]) 101 | assert(get_arg_gprs(state)[0:2] == ["&arg00", "?"]) 102 | 103 | def test_fp_int_aggregates_rv32ifd(): 104 | m = RVMachine(xlen=32, flen=64) 105 | # Float+float 106 | state = m.call([Struct(Double, Float)]) 107 | assert(get_arg_fprs(state)[0:3] == ["arg00[0:63]", "arg00[64:95]", "?"]) 108 | 109 | # Float+int, int+float 110 | state = m.call([Struct(Double, Int16)]) 111 | assert(get_arg_gprs(state)[0:2] == ["arg00[64:79]", "?"]) 112 | assert(get_arg_fprs(state)[0:2] == ["arg00[0:63]", "?"]) 113 | state = m.call([Struct(Int8, Double)]) 114 | assert(get_arg_gprs(state)[0:2] == ["arg00[0:7]", "?"]) 115 | assert(get_arg_fprs(state)[0:2] == ["arg00[64:127]", "?"]) 116 | 117 | # The "int" field can't be a small aggregate 118 | state = m.call([Struct(Struct(Int8, Int8), Float)]) 119 | assert(get_arg_gprs(state)[0:3] == ["arg00[0:31]", "arg00[32:63]", "?"]) 120 | assert(get_arg_fprs(state)[0] == "?") 121 | 122 | # Use integer calling convention if the int is greater than xlen or the 123 | # float greater than flen 124 | state = m.call([Struct(Int64, Float)]) 125 | assert(get_arg_gprs(state)[0:2] == ["&arg00", "?"]) 126 | assert(get_arg_fprs(state)[0] == "?") 127 | state = m.call([Struct(Int32, LongDouble)]) 128 | assert(get_arg_gprs(state)[0:2] == ["&arg00", "?"]) 129 | assert(get_arg_fprs(state)[0] == "?") 130 | 131 | # Check flattening 132 | equiv_args = [ 133 | [Struct(Int32, Struct(Double))], 134 | [Struct(Array(Int32, 1), Struct(Double))], 135 | [Struct(Array(Int32, 1), Array(Struct(Double), 1))], 136 | [Struct(Struct(Int32), Struct(), Struct(Double))], 137 | [Struct(Int32, Struct(Array(Double, 1)))], 138 | ] 139 | for args in equiv_args: 140 | state = m.call(args) 141 | assert(get_arg_gprs(state)[0:2] == ["arg00[0:31]", "?"]) 142 | assert(get_arg_fprs(state)[0:2] == ["arg00[64:127]", "?"]) 143 | 144 | def test_var_args_wrapper(): 145 | # Test that VarArgs can't be misused 146 | m = RVMachine(xlen=32) 147 | with pytest.raises(InvalidVarArgs): 148 | RVMachine().call([], VarArgs(Int32)) 149 | with pytest.raises(InvalidVarArgs): 150 | RVMachine().call([VarArgs(Int32), VarArgs(Int64)]) 151 | with pytest.raises(InvalidVarArgs): 152 | RVMachine().call([VarArgs(Int32), Int64]) 153 | 154 | def test_var_args(): 155 | m = RVMachine(xlen=32, flen=64) 156 | 157 | state = m.call([Int32, VarArgs(Int32, Struct(Int64, Double))]) 158 | assert(get_arg_gprs(state)[0:4] == ["arg00", "varg00", "&varg01", "?"]) 159 | 160 | # 2xlen aligned and sized varargs are passed in an aligned register pair 161 | state = m.call([Int32, VarArgs(Int64)]) 162 | assert(get_arg_gprs(state)[0:4] == ["arg00", "?", "varg00[0:31]", "varg00[32:63]"]) 163 | state = m.call([Int32, Int32, Int32, Int32, Int32, Int32, Int32, VarArgs(Int64)]) 164 | assert(get_arg_gprs(state)[6:8] == ["arg06", "?"]) 165 | assert(get_stack_objects(state) == ["varg00"]) 166 | 167 | # a 2xlen argument with alignment less than 2xlen isn't passed in an 168 | # aligned register pair 169 | state = m.call([VarArgs(Int32, Struct(Ptr32, Int32))]) 170 | assert(get_arg_gprs(state)[0:4] == ["varg00", "varg01[0:31]", "varg01[32:63]", "?"]) 171 | 172 | # Floating point varargs are always passed according to the integer 173 | # calling convention 174 | state = m.call([Float, VarArgs(Double, Struct(Int32, Float))]) 175 | assert(get_arg_gprs(state)[0:5] == ["varg00[0:31]", "varg00[32:63]", 176 | "varg01[0:31]", "varg01[32:63]", "?"]) 177 | assert(get_arg_fprs(state)[0:2] == ["arg00", "?"]) 178 | 179 | # Varargs should be promoted 180 | state = m.call([VarArgs(Float, Int8, UInt16)]) 181 | assert([str(state.gprs[10]), str(state.gprs[11]), str(state.gprs[12])] == 182 | ["FP32", "SInt32", "UInt32"]) 183 | 184 | def test_simple_usage(): 185 | m = RVMachine(xlen=32, flen=64) 186 | state = m.call([ 187 | Int32, 188 | Double, 189 | Struct(Int8, Array(Float, 1)), 190 | Struct(Array(Int8, 20)), 191 | Int64, 192 | Int64, 193 | Int64]) 194 | assert(get_arg_gprs(state) == ["arg00", "arg02[0:7]", "&arg03", "arg04[0:31]", 195 | "arg04[32:63]", "arg05[0:31]", "arg05[32:63]", "arg06[0:31]"]) 196 | assert(get_arg_fprs(state)[0:3] == ["arg01", "arg02[32:63]", "?"]) 197 | assert(len(state.stack) == 1) 198 | assert(state.typestr_or_name(state.stack[0]) == "arg06[32:63]") 199 | 200 | def test_large_return(): 201 | m = RVMachine(xlen=32) 202 | state = m.call([], Int128) 203 | assert(get_arg_gprs(state)[0:2] == ["&ret", "?"]) 204 | state = m.call([], Int32) 205 | assert(get_arg_gprs(state)[0] == "?") 206 | 207 | def test_ret_calculations(): 208 | m = RVMachine(xlen=32, flen=64) 209 | state = m.ret(Int32) 210 | assert(get_arg_gprs(state)[0:2] == ["ret", "?"]) 211 | 212 | state = m.ret(Int128) 213 | assert(get_arg_gprs(state)[0:2] == ["?", "?"]) 214 | assert(len(state.stack) == 0) 215 | 216 | state = m.ret(Struct(Int32, Double)) 217 | assert(get_arg_gprs(state)[0:2] == ["ret[0:31]", "?"]) 218 | assert(get_arg_fprs(state)[0:2] == ["ret[64:127]", "?"]) 219 | 220 | def test_stack_info(): 221 | m = RVMachine(xlen=32) 222 | state = m.call([Int32]*7 + [Double, Int64, Float, Struct(Int64, Int64)]) 223 | assert(str(state).splitlines()[-4:] == ["arg07[32:63] (oldsp+0)", 224 | "arg08 (oldsp+8)", "arg09 (oldsp+16)", "&arg10 (oldsp+20)"]) 225 | 226 | def test_random_int(): 227 | random.seed(14) 228 | assert(Int(8, True).random_literal() == '-74') 229 | assert(Int(8, False).random_literal() == '63u') 230 | assert(Int(32, True).random_literal() == '-1048936187') 231 | assert(Int(64, False).random_literal() == '1339710923952836751ull') 232 | 233 | def test_random_fp(): 234 | random.seed(20) 235 | assert(Float.random_literal() == '854.2f') 236 | assert(Double.random_literal() == '-468.1') 237 | assert(LongDouble.random_literal() == '786.5l') 238 | 239 | def test_random_ptr(): 240 | random.seed(30) 241 | assert(Ptr32.random_literal() == '(char*)0x4a08c720u') 242 | assert(Ptr64.random_literal() == '(char*)0x7b07fa39c6ab710ull') 243 | 244 | def test_random_struct(): 245 | random.seed(40) 246 | strct_ty = Struct(Int32, Ptr32, Float) 247 | strct_ty.name = 'foo' 248 | assert(strct_ty.random_literal() == 249 | '(struct foo){-2010704054, (char*)0x484d1466u, 361.3f}') 250 | 251 | def test_c_types(): 252 | assert(Int8.ctype() == 'int8_t') 253 | assert(UInt8.ctype() == 'uint8_t') 254 | assert(Char.ctype() == 'uint8_t') 255 | assert(Int16.ctype() == 'int16_t') 256 | assert(UInt16.ctype() == 'uint16_t') 257 | assert(Int32.ctype() == 'int32_t') 258 | assert(UInt32.ctype() == 'uint32_t') 259 | assert(Int64.ctype() == 'int64_t') 260 | assert(UInt64.ctype() == 'uint64_t') 261 | assert(Int128.ctype() == 'int128_t') 262 | assert(UInt128.ctype() == 'uint128_t') 263 | assert(Float.ctype() == 'float') 264 | assert(Double.ctype() == 'double') 265 | assert(LongDouble.ctype() == 'long double') 266 | assert(Ptr32.ctype() == 'char*') 267 | assert(Ptr64.ctype() == 'char*') 268 | strct_ty = Struct(LongDouble, Ptr32, UInt64) 269 | strct_ty.name = 'foo' 270 | assert(strct_ty.ctype() == 'struct foo') 271 | 272 | def test_cdecl(): 273 | strct_ty = Struct(LongDouble, Ptr32, UInt64) 274 | strct_ty.name = 'foo' 275 | assert(strct_ty.cdecl() == 276 | 'struct foo { long double fld0; char* fld1; uint64_t fld2; }') 277 | --------------------------------------------------------------------------------