1 /*
   2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.internal.foreign.abi.x64.sysv;
  24 
  25 import java.foreign.layout.Address;
  26 import java.foreign.layout.Group;
  27 import java.foreign.layout.Group.Kind;
  28 import java.foreign.layout.Layout;
  29 import java.foreign.layout.Padding;
  30 import java.foreign.layout.Sequence;
  31 import java.foreign.layout.Value;
  32 import java.util.ArrayList;
  33 import java.util.List;
  34 import java.util.function.BiConsumer;
  35 
  36 import jdk.internal.foreign.Util;
  37 import jdk.internal.foreign.abi.Argument;
  38 import jdk.internal.foreign.abi.ArgumentBinding;
  39 import jdk.internal.foreign.abi.CallingSequenceBuilder;
  40 import jdk.internal.foreign.abi.Storage;
  41 import jdk.internal.foreign.abi.StorageClass;
  42 import jdk.internal.foreign.abi.x64.ArgumentClass;
  43 import jdk.internal.foreign.abi.x64.SharedUtils;
  44 
  45 import static sun.security.action.GetBooleanAction.privilegedGetProperty;
  46 
  47 public class CallingSequenceBuilderImpl extends CallingSequenceBuilder {
  48 
  49     private static final SharedUtils.StorageDebugHelper storageDbgHelper = new SharedUtils.StorageDebugHelper(
  50             new String[] { "rdi", "rsi", "rdx", "rcx", "r8", "r9" },
  51             new String[] { "rax", "rdx" },
  52             new String[] { "st0", "st1" },
  53             SysVx64ABI.MAX_VECTOR_ARGUMENT_REGISTERS,
  54             SysVx64ABI.MAX_VECTOR_RETURN_REGISTERS
  55     );
  56 
  57     private static final boolean DEBUG =
  58         privilegedGetProperty("jdk.internal.foreign.abi.x64.sysv.DEBUG");
  59 
  60     // The AVX 512 enlightened ABI says "eight eightbytes"
  61     // Although AMD64 0.99.6 states 4 eightbytes
  62     private static final int MAX_AGGREGATE_REGS_SIZE = 8;
  63 
  64     private static final ArrayList<ArgumentClass> COMPLEX_X87_CLASSES;
  65 
  66     static {
  67         COMPLEX_X87_CLASSES = new ArrayList<>();
  68         COMPLEX_X87_CLASSES.add(ArgumentClass.X87);
  69         COMPLEX_X87_CLASSES.add(ArgumentClass.X87UP);
  70         COMPLEX_X87_CLASSES.add(ArgumentClass.X87);
  71         COMPLEX_X87_CLASSES.add(ArgumentClass.X87UP);
  72     }
  73 
  74     public CallingSequenceBuilderImpl(Layout layout) {
  75         this(layout, new StorageCalculator(false), new StorageCalculator(true));
  76     }
  77 
  78     private CallingSequenceBuilderImpl(Layout layout, StorageCalculator retCalculator, StorageCalculator argCalculator) {
  79         super(layout, retCalculator::addBindings, argCalculator::addBindings, argCalculator::addBindings);
  80     }
  81 
  82     @Override
  83     protected ArgumentInfo makeArgument(Layout layout, int pos, String name) {
  84         return new ArgumentInfo(layout, pos, name);
  85     }
  86 
  87     static class ArgumentInfo extends Argument {
  88         private final List<ArgumentClass> classes;
  89 
  90         public ArgumentInfo(Layout layout, int argumentIndex, String debugName) {
  91             super(layout, argumentIndex, debugName);
  92             this.classes = classifyType(layout);
  93         }
  94 
  95         public int getIntegerRegs() {
  96             return (int)classes.stream()
  97                     .filter(cl -> cl == ArgumentClass.INTEGER)
  98                     .count();
  99         }
 100 
 101         public int getVectorRegs() {
 102             return (int)classes.stream()
 103                     .filter(cl -> cl == ArgumentClass.SSE)
 104                     .count();
 105         }
 106 
 107         @Override
 108         public boolean inMemory() {
 109             return classes.stream().allMatch(this::isMemoryClass);
 110         }
 111 
 112         private boolean isMemoryClass(ArgumentClass cl) {
 113             return cl == ArgumentClass.MEMORY ||
 114                     (argumentIndex() != -1 &&
 115                             (cl == ArgumentClass.X87 || cl == ArgumentClass.X87UP));
 116         }
 117 
 118         public List<ArgumentClass> getClasses() {
 119             return classes;
 120         }
 121     }
 122 
 123     private static List<ArgumentClass> classifyValueType(Value type) {
 124         ArrayList<ArgumentClass> classes = new ArrayList<>();
 125 
 126         switch (type.kind()) {
 127             case INTEGRAL_SIGNED: case INTEGRAL_UNSIGNED:
 128                 classes.add(ArgumentClass.INTEGER);
 129                 // int128
 130                 long left = (type.bitsSize() / 8) - 8;
 131                 while (left > 0) {
 132                     classes.add(ArgumentClass.INTEGER);
 133                     left -= 8;
 134                 }
 135                 return classes;
 136             case FLOATING_POINT:
 137                 if ((type.bitsSize() / 8) > 8) {
 138                     classes.add(ArgumentClass.X87);
 139                     classes.add(ArgumentClass.X87UP);
 140                     return classes;
 141                 } else {
 142                     classes.add(ArgumentClass.SSE);
 143                     return classes;
 144                 }
 145             default:
 146                 throw new IllegalArgumentException("Type " + type + " is not yet supported");
 147         }
 148     }
 149 
 150     private static List<ArgumentClass> classifyArrayType(Sequence type) {
 151         long nWords = Util.alignUp((type.bitsSize() / 8), 8) / 8;
 152         if (nWords > MAX_AGGREGATE_REGS_SIZE) {
 153             return createMemoryClassArray(nWords);
 154         }
 155 
 156         ArrayList<ArgumentClass> classes = new ArrayList<>();
 157 
 158         for (long i = 0; i < nWords; i++) {
 159             classes.add(ArgumentClass.NO_CLASS);
 160         }
 161 
 162         long offset = 0;
 163         final long count = type.elementsSize();
 164         for (long idx = 0; idx < count; idx++) {
 165             Layout t = type.element();
 166             offset = SharedUtils.align(t, false, offset);
 167             List<ArgumentClass> subclasses = classifyType(t);
 168             if (subclasses.isEmpty()) {
 169                 return classes;
 170             }
 171 
 172             for (int i = 0; i < subclasses.size(); i++) {
 173                 int pos = (int)(offset / 8);
 174                 ArgumentClass newClass = classes.get(i + pos).merge(subclasses.get(i));
 175                 classes.set(i + pos, newClass);
 176             }
 177 
 178             offset += t.bitsSize() / 8;
 179         }
 180 
 181         for (int i = 0; i < classes.size(); i++) {
 182             ArgumentClass c = classes.get(i);
 183 
 184             if (c == ArgumentClass.MEMORY) {
 185                 return createMemoryClassArray(classes.size());
 186             }
 187 
 188             if (c == ArgumentClass.X87UP) {
 189                 if (i == 0) {
 190                     throw new IllegalArgumentException("Unexpected leading X87UP class");
 191                 }
 192 
 193                 if (classes.get(i - 1) != ArgumentClass.X87) {
 194                     return createMemoryClassArray(classes.size());
 195                 }
 196             }
 197         }
 198 
 199         if (classes.size() > 2) {
 200             if (classes.get(0) != ArgumentClass.SSE) {
 201                 return createMemoryClassArray(classes.size());
 202             }
 203 
 204             for (int i = 1; i < classes.size(); i++) {
 205                 if (classes.get(i) != ArgumentClass.SSEUP) {
 206                     return createMemoryClassArray(classes.size());
 207                 }
 208             }
 209         }
 210 
 211         return classes;
 212     }
 213 
 214     // TODO: handle zero length arrays
 215     // TODO: Handle nested structs (and primitives)
 216     private static List<ArgumentClass> classifyStructType(Group type) {
 217         long nWords = Util.alignUp((type.bitsSize() / 8), 8) / 8;
 218         if (nWords > MAX_AGGREGATE_REGS_SIZE) {
 219             return createMemoryClassArray(nWords);
 220         }
 221 
 222         ArrayList<ArgumentClass> classes = new ArrayList<>();
 223 
 224         for (long i = 0; i < nWords; i++) {
 225             classes.add(ArgumentClass.NO_CLASS);
 226         }
 227 
 228         long offset = 0;
 229         final int count = type.elements().size();
 230         for (int idx = 0; idx < count; idx++) {
 231             Layout t = type.elements().get(idx);
 232             if (t instanceof Padding) {
 233                 continue;
 234             }
 235             // ignore zero-length array for now
 236             // TODO: handle zero length arrays here
 237             if (t instanceof Sequence) {
 238                 if (((Sequence) t).elementsSize() == 0) {
 239                     continue;
 240                 }
 241             }
 242             offset = SharedUtils.align(t, false, offset);
 243             List<ArgumentClass> subclasses = classifyType(t);
 244             if (subclasses.isEmpty()) {
 245                 return classes;
 246             }
 247 
 248             for (int i = 0; i < subclasses.size(); i++) {
 249                 int pos = (int)(offset / 8);
 250                 ArgumentClass newClass = classes.get(i + pos).merge(subclasses.get(i));
 251                 classes.set(i + pos, newClass);
 252             }
 253 
 254             // TODO: validate union strategy is sound
 255             if (type.kind() != Kind.UNION) {
 256                 offset += t.bitsSize() / 8;
 257             }
 258         }
 259 
 260         for (int i = 0; i < classes.size(); i++) {
 261             ArgumentClass c = classes.get(i);
 262 
 263             if (c == ArgumentClass.MEMORY) {
 264                 return createMemoryClassArray(classes.size());
 265             }
 266 
 267             if (c == ArgumentClass.X87UP) {
 268                 if (i == 0) {
 269                     throw new IllegalArgumentException("Unexpected leading X87UP class");
 270                 }
 271 
 272                 if (classes.get(i - 1) != ArgumentClass.X87) {
 273                     return createMemoryClassArray(classes.size());
 274                 }
 275             }
 276         }
 277 
 278         if (classes.size() > 2) {
 279             if (classes.get(0) != ArgumentClass.SSE) {
 280                 return createMemoryClassArray(classes.size());
 281             }
 282 
 283             for (int i = 1; i < classes.size(); i++) {
 284                 if (classes.get(i) != ArgumentClass.SSEUP) {
 285                     return createMemoryClassArray(classes.size());
 286                 }
 287             }
 288         }
 289 
 290         return classes;
 291     }
 292 
 293     private static List<ArgumentClass> classifyType(Layout type) {
 294         try {
 295             if (type instanceof Value) {
 296                 return classifyValueType((Value) type);
 297             } else if (type instanceof Address) {
 298                 ArrayList<ArgumentClass> classes = new ArrayList<>();
 299                 classes.add(ArgumentClass.INTEGER);
 300                 return classes;
 301             } else if (type instanceof Sequence) {
 302                 return classifyArrayType((Sequence) type);
 303             } else if (type instanceof Group) {
 304                 return type.name().isPresent() && type.name().get().equals("LongDoubleComplex") ?
 305                         COMPLEX_X87_CLASSES :
 306                         classifyStructType((Group) type);
 307             } else {
 308                 throw new IllegalArgumentException("Unhandled type " + type);
 309             }
 310         } catch (UnsupportedOperationException e) {
 311             System.err.println("Failed to classify layout: " + type);
 312             throw e;
 313         }
 314     }
 315 
 316     private static List<ArgumentClass> createMemoryClassArray(long n) {
 317         ArrayList<ArgumentClass> classes = new ArrayList<>();
 318         for (int i = 0; i < n; i++) {
 319             classes.add(ArgumentClass.MEMORY);
 320         }
 321 
 322         return classes;
 323     }
 324 
 325     static class StorageCalculator {
 326         private final boolean forArguments;
 327 
 328         private int nIntegerRegs = 0;
 329         private int nVectorRegs = 0;
 330         private int nX87Regs = 0;
 331         private long stackOffset = 0;
 332 
 333         StorageCalculator(boolean forArguments) {
 334             this.forArguments = forArguments;
 335         }
 336 
 337         public void addBindings(Argument arg, BiConsumer<StorageClass, ArgumentBinding> bindingConsumer) {
 338             ArgumentInfo info = (ArgumentInfo)arg;
 339             if (info.inMemory() ||
 340                     nIntegerRegs + info.getIntegerRegs() > (forArguments ? SysVx64ABI.MAX_INTEGER_ARGUMENT_REGISTERS : SysVx64ABI.MAX_INTEGER_RETURN_REGISTERS) ||
 341                     nVectorRegs + info.getVectorRegs() > (forArguments ? SysVx64ABI.MAX_VECTOR_ARGUMENT_REGISTERS : SysVx64ABI.MAX_VECTOR_RETURN_REGISTERS)) {
 342                 // stack
 343 
 344                 long alignment = Math.max(SharedUtils.alignment(info.layout(), true), 8);
 345 
 346                 long newStackOffset = Util.alignUp(stackOffset, alignment);
 347                 stackOffset = newStackOffset;
 348 
 349                 long tmpStackOffset = stackOffset;
 350                 for (int i = 0; i < info.getClasses().size(); i++) {
 351                     Storage storage = new Storage(StorageClass.STACK_ARGUMENT_SLOT, tmpStackOffset / 8, 8);
 352                     bindingConsumer.accept(StorageClass.STACK_ARGUMENT_SLOT, new ArgumentBinding(storage, info, i * 8));
 353 
 354                     if (DEBUG) {
 355                         System.out.println("Argument " + info.name() + " will be passed on stack at offset " + tmpStackOffset);
 356                     }
 357 
 358                     tmpStackOffset += 8;
 359                 }
 360 
 361                 stackOffset += info.layout().bitsSize() / 8;
 362             } else {
 363                 // regs
 364                 for (int i = 0; i < info.getClasses().size(); i++) {
 365                     Storage storage;
 366 
 367                     ArgumentClass c = info.getClasses().get(i);
 368 
 369                     switch (c) {
 370                     case INTEGER:
 371                         storage = new Storage(forArguments ? StorageClass.INTEGER_ARGUMENT_REGISTER : StorageClass.INTEGER_RETURN_REGISTER, nIntegerRegs++, SharedUtils.INTEGER_REGISTER_SIZE);
 372                         bindingConsumer.accept(storage.getStorageClass(), new ArgumentBinding(storage, info, i * 8));
 373 
 374                         if (DEBUG) {
 375                             System.out.println("Argument " + info.name() + " will be passed in register " +
 376                                     storageDbgHelper.getStorageName(storage));
 377                         }
 378                         break;
 379 
 380                     case SSE: {
 381                         int width = 8;
 382 
 383                         for (int j = i + 1; j < info.getClasses().size(); j++) {
 384                             if (info.getClasses().get(j) == ArgumentClass.SSEUP) {
 385                                 width += 8;
 386                             }
 387                         }
 388 
 389                         if (width > 64) {
 390                             throw new IllegalArgumentException((width * 8) + "-bit vector arguments not supported");
 391                         }
 392 
 393                         storage = new Storage(forArguments ? StorageClass.VECTOR_ARGUMENT_REGISTER : StorageClass.VECTOR_RETURN_REGISTER,
 394                                 nVectorRegs++, width, SharedUtils.VECTOR_REGISTER_SIZE);
 395 
 396                         bindingConsumer.accept(storage.getStorageClass(), new ArgumentBinding(storage, info, i * 8));
 397 
 398                         if (DEBUG) {
 399                             System.out.println("Argument " + info.name() + " will be passed in register " +
 400                                     storageDbgHelper.getStorageName(storage));
 401                         }
 402                         break;
 403                     }
 404 
 405                     case SSEUP:
 406                         break;
 407 
 408                     case X87: {
 409                         int width = 8;
 410 
 411                         if (i < info.getClasses().size() && info.getClasses().get(i + 1) == ArgumentClass.X87UP) {
 412                             width += 8;
 413                         }
 414 
 415                         assert !forArguments;
 416 
 417                         storage = new Storage(StorageClass.X87_RETURN_REGISTER, nX87Regs++, width, SharedUtils.X87_REGISTER_SIZE);
 418                         bindingConsumer.accept(storage.getStorageClass(), new ArgumentBinding(storage, info, i * 8));
 419 
 420                         if (DEBUG) {
 421                             System.out.println("Argument " + info.name() + " will be passed in register " +
 422                                     storageDbgHelper.getStorageName(storage));
 423                         }
 424                         break;
 425                     }
 426 
 427                     case X87UP:
 428                         break;
 429 
 430                     default:
 431                         throw new UnsupportedOperationException("Unhandled class " + c);
 432                     }
 433                 }
 434             }
 435         }
 436     }
 437 }