| /* |
| * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2014, 2025, Red Hat Inc. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| #include "asm/macroAssembler.hpp" |
| #include "asm/macroAssembler.inline.hpp" |
| #include "asm/register.hpp" |
| #include "atomic_aarch64.hpp" |
| #include "compiler/oopMap.hpp" |
| #include "gc/shared/barrierSet.hpp" |
| #include "gc/shared/barrierSetAssembler.hpp" |
| #include "gc/shared/gc_globals.hpp" |
| #include "gc/shared/tlab_globals.hpp" |
| #include "interpreter/interpreter.hpp" |
| #include "memory/universe.hpp" |
| #include "nativeInst_aarch64.hpp" |
| #include "oops/instanceOop.hpp" |
| #include "oops/method.hpp" |
| #include "oops/objArrayKlass.hpp" |
| #include "oops/oop.inline.hpp" |
| #include "prims/methodHandles.hpp" |
| #include "prims/upcallLinker.hpp" |
| #include "runtime/arguments.hpp" |
| #include "runtime/atomic.hpp" |
| #include "runtime/continuation.hpp" |
| #include "runtime/continuationEntry.inline.hpp" |
| #include "runtime/frame.inline.hpp" |
| #include "runtime/handles.inline.hpp" |
| #include "runtime/javaThread.hpp" |
| #include "runtime/sharedRuntime.hpp" |
| #include "runtime/stubCodeGenerator.hpp" |
| #include "runtime/stubRoutines.hpp" |
| #include "utilities/align.hpp" |
| #include "utilities/checkedCast.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| #include "utilities/intpow.hpp" |
| #include "utilities/powerOfTwo.hpp" |
| #ifdef COMPILER2 |
| #include "opto/runtime.hpp" |
| #endif |
| #if INCLUDE_ZGC |
| #include "gc/z/zThreadLocalData.hpp" |
| #endif |
| |
| // Declaration and definition of StubGenerator (no .hpp file). |
| // For a more detailed description of the stub routine structure |
| // see the comment in stubRoutines.hpp |
| |
| #undef __ |
| #define __ _masm-> |
| |
| #ifdef PRODUCT |
| #define BLOCK_COMMENT(str) /* nothing */ |
| #else |
| #define BLOCK_COMMENT(str) __ block_comment(str) |
| #endif |
| |
| #define BIND(label) bind(label); BLOCK_COMMENT(#label ":") |
| |
| // Stub Code definitions |
| |
| class StubGenerator: public StubCodeGenerator { |
| private: |
| |
| #ifdef PRODUCT |
| #define inc_counter_np(counter) ((void)0) |
| #else |
| void inc_counter_np_(uint& counter) { |
| __ incrementw(ExternalAddress((address)&counter)); |
| } |
| #define inc_counter_np(counter) \ |
| BLOCK_COMMENT("inc_counter " #counter); \ |
| inc_counter_np_(counter); |
| #endif |
| |
| // Call stubs are used to call Java from C |
| // |
| // Arguments: |
| // c_rarg0: call wrapper address address |
| // c_rarg1: result address |
| // c_rarg2: result type BasicType |
| // c_rarg3: method Method* |
| // c_rarg4: (interpreter) entry point address |
| // c_rarg5: parameters intptr_t* |
| // c_rarg6: parameter size (in words) int |
| // c_rarg7: thread Thread* |
| // |
| // There is no return from the stub itself as any Java result |
| // is written to result |
| // |
| // we save r30 (lr) as the return PC at the base of the frame and |
| // link r29 (fp) below it as the frame pointer installing sp (r31) |
| // into fp. |
| // |
| // we save r0-r7, which accounts for all the c arguments. |
| // |
| // TODO: strictly do we need to save them all? they are treated as |
| // volatile by C so could we omit saving the ones we are going to |
| // place in global registers (thread? method?) or those we only use |
| // during setup of the Java call? |
| // |
| // we don't need to save r8 which C uses as an indirect result location |
| // return register. |
| // |
| // we don't need to save r9-r15 which both C and Java treat as |
| // volatile |
| // |
| // we don't need to save r16-18 because Java does not use them |
| // |
| // we save r19-r28 which Java uses as scratch registers and C |
| // expects to be callee-save |
| // |
| // we save the bottom 64 bits of each value stored in v8-v15; it is |
| // the responsibility of the caller to preserve larger values. |
| // |
| // so the stub frame looks like this when we enter Java code |
| // |
| // [ return_from_Java ] <--- sp |
| // [ argument word n ] |
| // ... |
| // -29 [ argument word 1 ] |
| // -28 [ saved Floating-point Control Register ] |
| // -26 [ saved v15 ] <--- sp_after_call |
| // -25 [ saved v14 ] |
| // -24 [ saved v13 ] |
| // -23 [ saved v12 ] |
| // -22 [ saved v11 ] |
| // -21 [ saved v10 ] |
| // -20 [ saved v9 ] |
| // -19 [ saved v8 ] |
| // -18 [ saved r28 ] |
| // -17 [ saved r27 ] |
| // -16 [ saved r26 ] |
| // -15 [ saved r25 ] |
| // -14 [ saved r24 ] |
| // -13 [ saved r23 ] |
| // -12 [ saved r22 ] |
| // -11 [ saved r21 ] |
| // -10 [ saved r20 ] |
| // -9 [ saved r19 ] |
| // -8 [ call wrapper (r0) ] |
| // -7 [ result (r1) ] |
| // -6 [ result type (r2) ] |
| // -5 [ method (r3) ] |
| // -4 [ entry point (r4) ] |
| // -3 [ parameters (r5) ] |
| // -2 [ parameter size (r6) ] |
| // -1 [ thread (r7) ] |
| // 0 [ saved fp (r29) ] <--- fp == saved sp (r31) |
| // 1 [ saved lr (r30) ] |
| |
| // Call stub stack layout word offsets from fp |
| enum call_stub_layout { |
| sp_after_call_off = -28, |
| |
| fpcr_off = sp_after_call_off, |
| d15_off = -26, |
| d13_off = -24, |
| d11_off = -22, |
| d9_off = -20, |
| |
| r28_off = -18, |
| r26_off = -16, |
| r24_off = -14, |
| r22_off = -12, |
| r20_off = -10, |
| call_wrapper_off = -8, |
| result_off = -7, |
| result_type_off = -6, |
| method_off = -5, |
| entry_point_off = -4, |
| parameter_size_off = -2, |
| thread_off = -1, |
| fp_f = 0, |
| retaddr_off = 1, |
| }; |
| |
| address generate_call_stub(address& return_address) { |
| assert((int)frame::entry_frame_after_call_words == -(int)sp_after_call_off + 1 && |
| (int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off, |
| "adjust this code"); |
| |
| StubGenStubId stub_id = StubGenStubId::call_stub_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| const Address sp_after_call (rfp, sp_after_call_off * wordSize); |
| |
| const Address fpcr_save (rfp, fpcr_off * wordSize); |
| const Address call_wrapper (rfp, call_wrapper_off * wordSize); |
| const Address result (rfp, result_off * wordSize); |
| const Address result_type (rfp, result_type_off * wordSize); |
| const Address method (rfp, method_off * wordSize); |
| const Address entry_point (rfp, entry_point_off * wordSize); |
| const Address parameter_size(rfp, parameter_size_off * wordSize); |
| |
| const Address thread (rfp, thread_off * wordSize); |
| |
| const Address d15_save (rfp, d15_off * wordSize); |
| const Address d13_save (rfp, d13_off * wordSize); |
| const Address d11_save (rfp, d11_off * wordSize); |
| const Address d9_save (rfp, d9_off * wordSize); |
| |
| const Address r28_save (rfp, r28_off * wordSize); |
| const Address r26_save (rfp, r26_off * wordSize); |
| const Address r24_save (rfp, r24_off * wordSize); |
| const Address r22_save (rfp, r22_off * wordSize); |
| const Address r20_save (rfp, r20_off * wordSize); |
| |
| // stub code |
| |
| address aarch64_entry = __ pc(); |
| |
| // set up frame and move sp to end of save area |
| __ enter(); |
| __ sub(sp, rfp, -sp_after_call_off * wordSize); |
| |
| // save register parameters and Java scratch/global registers |
| // n.b. we save thread even though it gets installed in |
| // rthread because we want to sanity check rthread later |
| __ str(c_rarg7, thread); |
| __ strw(c_rarg6, parameter_size); |
| __ stp(c_rarg4, c_rarg5, entry_point); |
| __ stp(c_rarg2, c_rarg3, result_type); |
| __ stp(c_rarg0, c_rarg1, call_wrapper); |
| |
| __ stp(r20, r19, r20_save); |
| __ stp(r22, r21, r22_save); |
| __ stp(r24, r23, r24_save); |
| __ stp(r26, r25, r26_save); |
| __ stp(r28, r27, r28_save); |
| |
| __ stpd(v9, v8, d9_save); |
| __ stpd(v11, v10, d11_save); |
| __ stpd(v13, v12, d13_save); |
| __ stpd(v15, v14, d15_save); |
| |
| __ get_fpcr(rscratch1); |
| __ str(rscratch1, fpcr_save); |
| // Set FPCR to the state we need. We do want Round to Nearest. We |
| // don't want non-IEEE rounding modes or floating-point traps. |
| __ bfi(rscratch1, zr, 22, 4); // Clear DN, FZ, and Rmode |
| __ bfi(rscratch1, zr, 8, 5); // Clear exception-control bits (8-12) |
| __ set_fpcr(rscratch1); |
| |
| // install Java thread in global register now we have saved |
| // whatever value it held |
| __ mov(rthread, c_rarg7); |
| // And method |
| __ mov(rmethod, c_rarg3); |
| |
| // set up the heapbase register |
| __ reinit_heapbase(); |
| |
| #ifdef ASSERT |
| // make sure we have no pending exceptions |
| { |
| Label L; |
| __ ldr(rscratch1, Address(rthread, in_bytes(Thread::pending_exception_offset()))); |
| __ cmp(rscratch1, (u1)NULL_WORD); |
| __ br(Assembler::EQ, L); |
| __ stop("StubRoutines::call_stub: entered with pending exception"); |
| __ BIND(L); |
| } |
| #endif |
| // pass parameters if any |
| __ mov(esp, sp); |
| __ sub(rscratch1, sp, c_rarg6, ext::uxtw, LogBytesPerWord); // Move SP out of the way |
| __ andr(sp, rscratch1, -2 * wordSize); |
| |
| BLOCK_COMMENT("pass parameters if any"); |
| Label parameters_done; |
| // parameter count is still in c_rarg6 |
| // and parameter pointer identifying param 1 is in c_rarg5 |
| __ cbzw(c_rarg6, parameters_done); |
| |
| address loop = __ pc(); |
| __ ldr(rscratch1, Address(__ post(c_rarg5, wordSize))); |
| __ subsw(c_rarg6, c_rarg6, 1); |
| __ push(rscratch1); |
| __ br(Assembler::GT, loop); |
| |
| __ BIND(parameters_done); |
| |
| // call Java entry -- passing methdoOop, and current sp |
| // rmethod: Method* |
| // r19_sender_sp: sender sp |
| BLOCK_COMMENT("call Java function"); |
| __ mov(r19_sender_sp, sp); |
| __ blr(c_rarg4); |
| |
| // we do this here because the notify will already have been done |
| // if we get to the next instruction via an exception |
| // |
| // n.b. adding this instruction here affects the calculation of |
| // whether or not a routine returns to the call stub (used when |
| // doing stack walks) since the normal test is to check the return |
| // pc against the address saved below. so we may need to allow for |
| // this extra instruction in the check. |
| |
| // save current address for use by exception handling code |
| |
| return_address = __ pc(); |
| |
| // store result depending on type (everything that is not |
| // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT) |
| // n.b. this assumes Java returns an integral result in r0 |
| // and a floating result in j_farg0 |
| __ ldr(j_rarg2, result); |
| Label is_long, is_float, is_double, exit; |
| __ ldr(j_rarg1, result_type); |
| __ cmp(j_rarg1, (u1)T_OBJECT); |
| __ br(Assembler::EQ, is_long); |
| __ cmp(j_rarg1, (u1)T_LONG); |
| __ br(Assembler::EQ, is_long); |
| __ cmp(j_rarg1, (u1)T_FLOAT); |
| __ br(Assembler::EQ, is_float); |
| __ cmp(j_rarg1, (u1)T_DOUBLE); |
| __ br(Assembler::EQ, is_double); |
| |
| // handle T_INT case |
| __ strw(r0, Address(j_rarg2)); |
| |
| __ BIND(exit); |
| |
| // pop parameters |
| __ sub(esp, rfp, -sp_after_call_off * wordSize); |
| |
| #ifdef ASSERT |
| // verify that threads correspond |
| { |
| Label L, S; |
| __ ldr(rscratch1, thread); |
| __ cmp(rthread, rscratch1); |
| __ br(Assembler::NE, S); |
| __ get_thread(rscratch1); |
| __ cmp(rthread, rscratch1); |
| __ br(Assembler::EQ, L); |
| __ BIND(S); |
| __ stop("StubRoutines::call_stub: threads must correspond"); |
| __ BIND(L); |
| } |
| #endif |
| |
| __ pop_cont_fastpath(rthread); |
| |
| // restore callee-save registers |
| __ ldpd(v15, v14, d15_save); |
| __ ldpd(v13, v12, d13_save); |
| __ ldpd(v11, v10, d11_save); |
| __ ldpd(v9, v8, d9_save); |
| |
| __ ldp(r28, r27, r28_save); |
| __ ldp(r26, r25, r26_save); |
| __ ldp(r24, r23, r24_save); |
| __ ldp(r22, r21, r22_save); |
| __ ldp(r20, r19, r20_save); |
| |
| // restore fpcr |
| __ ldr(rscratch1, fpcr_save); |
| __ set_fpcr(rscratch1); |
| |
| __ ldp(c_rarg0, c_rarg1, call_wrapper); |
| __ ldrw(c_rarg2, result_type); |
| __ ldr(c_rarg3, method); |
| __ ldp(c_rarg4, c_rarg5, entry_point); |
| __ ldp(c_rarg6, c_rarg7, parameter_size); |
| |
| // leave frame and return to caller |
| __ leave(); |
| __ ret(lr); |
| |
| // handle return types different from T_INT |
| |
| __ BIND(is_long); |
| __ str(r0, Address(j_rarg2, 0)); |
| __ br(Assembler::AL, exit); |
| |
| __ BIND(is_float); |
| __ strs(j_farg0, Address(j_rarg2, 0)); |
| __ br(Assembler::AL, exit); |
| |
| __ BIND(is_double); |
| __ strd(j_farg0, Address(j_rarg2, 0)); |
| __ br(Assembler::AL, exit); |
| |
| return start; |
| } |
| |
| // Return point for a Java call if there's an exception thrown in |
| // Java code. The exception is caught and transformed into a |
| // pending exception stored in JavaThread that can be tested from |
| // within the VM. |
| // |
| // Note: Usually the parameters are removed by the callee. In case |
| // of an exception crossing an activation frame boundary, that is |
| // not the case if the callee is compiled code => need to setup the |
| // rsp. |
| // |
| // r0: exception oop |
| |
| address generate_catch_exception() { |
| StubGenStubId stub_id = StubGenStubId::catch_exception_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| // same as in generate_call_stub(): |
| const Address sp_after_call(rfp, sp_after_call_off * wordSize); |
| const Address thread (rfp, thread_off * wordSize); |
| |
| #ifdef ASSERT |
| // verify that threads correspond |
| { |
| Label L, S; |
| __ ldr(rscratch1, thread); |
| __ cmp(rthread, rscratch1); |
| __ br(Assembler::NE, S); |
| __ get_thread(rscratch1); |
| __ cmp(rthread, rscratch1); |
| __ br(Assembler::EQ, L); |
| __ bind(S); |
| __ stop("StubRoutines::catch_exception: threads must correspond"); |
| __ bind(L); |
| } |
| #endif |
| |
| // set pending exception |
| __ verify_oop(r0); |
| |
| __ str(r0, Address(rthread, Thread::pending_exception_offset())); |
| __ mov(rscratch1, (address)__FILE__); |
| __ str(rscratch1, Address(rthread, Thread::exception_file_offset())); |
| __ movw(rscratch1, (int)__LINE__); |
| __ strw(rscratch1, Address(rthread, Thread::exception_line_offset())); |
| |
| // complete return to VM |
| assert(StubRoutines::_call_stub_return_address != nullptr, |
| "_call_stub_return_address must have been generated before"); |
| __ b(StubRoutines::_call_stub_return_address); |
| |
| return start; |
| } |
| |
| // Continuation point for runtime calls returning with a pending |
| // exception. The pending exception check happened in the runtime |
| // or native call stub. The pending exception in Thread is |
| // converted into a Java-level exception. |
| // |
| // Contract with Java-level exception handlers: |
| // r0: exception |
| // r3: throwing pc |
| // |
| // NOTE: At entry of this stub, exception-pc must be in LR !! |
| |
| // NOTE: this is always used as a jump target within generated code |
| // so it just needs to be generated code with no x86 prolog |
| |
| address generate_forward_exception() { |
| StubGenStubId stub_id = StubGenStubId::forward_exception_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| // Upon entry, LR points to the return address returning into |
| // Java (interpreted or compiled) code; i.e., the return address |
| // becomes the throwing pc. |
| // |
| // Arguments pushed before the runtime call are still on the stack |
| // but the exception handler will reset the stack pointer -> |
| // ignore them. A potential result in registers can be ignored as |
| // well. |
| |
| #ifdef ASSERT |
| // make sure this code is only executed if there is a pending exception |
| { |
| Label L; |
| __ ldr(rscratch1, Address(rthread, Thread::pending_exception_offset())); |
| __ cbnz(rscratch1, L); |
| __ stop("StubRoutines::forward exception: no pending exception (1)"); |
| __ bind(L); |
| } |
| #endif |
| |
| // compute exception handler into r19 |
| |
| // call the VM to find the handler address associated with the |
| // caller address. pass thread in r0 and caller pc (ret address) |
| // in r1. n.b. the caller pc is in lr, unlike x86 where it is on |
| // the stack. |
| __ mov(c_rarg1, lr); |
| // lr will be trashed by the VM call so we move it to R19 |
| // (callee-saved) because we also need to pass it to the handler |
| // returned by this call. |
| __ mov(r19, lr); |
| BLOCK_COMMENT("call exception_handler_for_return_address"); |
| __ call_VM_leaf(CAST_FROM_FN_PTR(address, |
| SharedRuntime::exception_handler_for_return_address), |
| rthread, c_rarg1); |
| // Reinitialize the ptrue predicate register, in case the external runtime |
| // call clobbers ptrue reg, as we may return to SVE compiled code. |
| __ reinitialize_ptrue(); |
| |
| // we should not really care that lr is no longer the callee |
| // address. we saved the value the handler needs in r19 so we can |
| // just copy it to r3. however, the C2 handler will push its own |
| // frame and then calls into the VM and the VM code asserts that |
| // the PC for the frame above the handler belongs to a compiled |
| // Java method. So, we restore lr here to satisfy that assert. |
| __ mov(lr, r19); |
| // setup r0 & r3 & clear pending exception |
| __ mov(r3, r19); |
| __ mov(r19, r0); |
| __ ldr(r0, Address(rthread, Thread::pending_exception_offset())); |
| __ str(zr, Address(rthread, Thread::pending_exception_offset())); |
| |
| #ifdef ASSERT |
| // make sure exception is set |
| { |
| Label L; |
| __ cbnz(r0, L); |
| __ stop("StubRoutines::forward exception: no pending exception (2)"); |
| __ bind(L); |
| } |
| #endif |
| |
| // continue at exception handler |
| // r0: exception |
| // r3: throwing pc |
| // r19: exception handler |
| __ verify_oop(r0); |
| __ br(r19); |
| |
| return start; |
| } |
| |
| // Non-destructive plausibility checks for oops |
| // |
| // Arguments: |
| // r0: oop to verify |
| // rscratch1: error message |
| // |
| // Stack after saving c_rarg3: |
| // [tos + 0]: saved c_rarg3 |
| // [tos + 1]: saved c_rarg2 |
| // [tos + 2]: saved lr |
| // [tos + 3]: saved rscratch2 |
| // [tos + 4]: saved r0 |
| // [tos + 5]: saved rscratch1 |
| address generate_verify_oop() { |
| StubGenStubId stub_id = StubGenStubId::verify_oop_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Label exit, error; |
| |
| // save c_rarg2 and c_rarg3 |
| __ stp(c_rarg3, c_rarg2, Address(__ pre(sp, -16))); |
| |
| // __ incrementl(ExternalAddress((address) StubRoutines::verify_oop_count_addr())); |
| __ lea(c_rarg2, ExternalAddress((address) StubRoutines::verify_oop_count_addr())); |
| __ ldr(c_rarg3, Address(c_rarg2)); |
| __ add(c_rarg3, c_rarg3, 1); |
| __ str(c_rarg3, Address(c_rarg2)); |
| |
| // object is in r0 |
| // make sure object is 'reasonable' |
| __ cbz(r0, exit); // if obj is null it is OK |
| |
| BarrierSetAssembler* bs_asm = BarrierSet::barrier_set()->barrier_set_assembler(); |
| bs_asm->check_oop(_masm, r0, c_rarg2, c_rarg3, error); |
| |
| // return if everything seems ok |
| __ bind(exit); |
| |
| __ ldp(c_rarg3, c_rarg2, Address(__ post(sp, 16))); |
| __ ret(lr); |
| |
| // handle errors |
| __ bind(error); |
| __ ldp(c_rarg3, c_rarg2, Address(__ post(sp, 16))); |
| |
| __ push(RegSet::range(r0, r29), sp); |
| // debug(char* msg, int64_t pc, int64_t regs[]) |
| __ mov(c_rarg0, rscratch1); // pass address of error message |
| __ mov(c_rarg1, lr); // pass return address |
| __ mov(c_rarg2, sp); // pass address of regs on stack |
| #ifndef PRODUCT |
| assert(frame::arg_reg_save_area_bytes == 0, "not expecting frame reg save area"); |
| #endif |
| BLOCK_COMMENT("call MacroAssembler::debug"); |
| __ mov(rscratch1, CAST_FROM_FN_PTR(address, MacroAssembler::debug64)); |
| __ blr(rscratch1); |
| __ hlt(0); |
| |
| return start; |
| } |
| |
| // Generate indices for iota vector. |
| address generate_iota_indices(StubGenStubId stub_id) { |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| // B |
| __ emit_data64(0x0706050403020100, relocInfo::none); |
| __ emit_data64(0x0F0E0D0C0B0A0908, relocInfo::none); |
| // H |
| __ emit_data64(0x0003000200010000, relocInfo::none); |
| __ emit_data64(0x0007000600050004, relocInfo::none); |
| // S |
| __ emit_data64(0x0000000100000000, relocInfo::none); |
| __ emit_data64(0x0000000300000002, relocInfo::none); |
| // D |
| __ emit_data64(0x0000000000000000, relocInfo::none); |
| __ emit_data64(0x0000000000000001, relocInfo::none); |
| // S - FP |
| __ emit_data64(0x3F80000000000000, relocInfo::none); // 0.0f, 1.0f |
| __ emit_data64(0x4040000040000000, relocInfo::none); // 2.0f, 3.0f |
| // D - FP |
| __ emit_data64(0x0000000000000000, relocInfo::none); // 0.0d |
| __ emit_data64(0x3FF0000000000000, relocInfo::none); // 1.0d |
| return start; |
| } |
| |
| // The inner part of zero_words(). This is the bulk operation, |
| // zeroing words in blocks, possibly using DC ZVA to do it. The |
| // caller is responsible for zeroing the last few words. |
| // |
| // Inputs: |
| // r10: the HeapWord-aligned base address of an array to zero. |
| // r11: the count in HeapWords, r11 > 0. |
| // |
| // Returns r10 and r11, adjusted for the caller to clear. |
| // r10: the base address of the tail of words left to clear. |
| // r11: the number of words in the tail. |
| // r11 < MacroAssembler::zero_words_block_size. |
| |
| address generate_zero_blocks() { |
| Label done; |
| Label base_aligned; |
| |
| Register base = r10, cnt = r11; |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::zero_blocks_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| if (UseBlockZeroing) { |
| int zva_length = VM_Version::zva_length(); |
| |
| // Ensure ZVA length can be divided by 16. This is required by |
| // the subsequent operations. |
| assert (zva_length % 16 == 0, "Unexpected ZVA Length"); |
| |
| __ tbz(base, 3, base_aligned); |
| __ str(zr, Address(__ post(base, 8))); |
| __ sub(cnt, cnt, 1); |
| __ bind(base_aligned); |
| |
| // Ensure count >= zva_length * 2 so that it still deserves a zva after |
| // alignment. |
| Label small; |
| int low_limit = MAX2(zva_length * 2, (int)BlockZeroingLowLimit); |
| __ subs(rscratch1, cnt, low_limit >> 3); |
| __ br(Assembler::LT, small); |
| __ zero_dcache_blocks(base, cnt); |
| __ bind(small); |
| } |
| |
| { |
| // Number of stp instructions we'll unroll |
| const int unroll = |
| MacroAssembler::zero_words_block_size / 2; |
| // Clear the remaining blocks. |
| Label loop; |
| __ subs(cnt, cnt, unroll * 2); |
| __ br(Assembler::LT, done); |
| __ bind(loop); |
| for (int i = 0; i < unroll; i++) |
| __ stp(zr, zr, __ post(base, 16)); |
| __ subs(cnt, cnt, unroll * 2); |
| __ br(Assembler::GE, loop); |
| __ bind(done); |
| __ add(cnt, cnt, unroll * 2); |
| } |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| |
| typedef enum { |
| copy_forwards = 1, |
| copy_backwards = -1 |
| } copy_direction; |
| |
| // Helper object to reduce noise when telling the GC barriers how to perform loads and stores |
| // for arraycopy stubs. |
| class ArrayCopyBarrierSetHelper : StackObj { |
| BarrierSetAssembler* _bs_asm; |
| MacroAssembler* _masm; |
| DecoratorSet _decorators; |
| BasicType _type; |
| Register _gct1; |
| Register _gct2; |
| Register _gct3; |
| FloatRegister _gcvt1; |
| FloatRegister _gcvt2; |
| FloatRegister _gcvt3; |
| |
| public: |
| ArrayCopyBarrierSetHelper(MacroAssembler* masm, |
| DecoratorSet decorators, |
| BasicType type, |
| Register gct1, |
| Register gct2, |
| Register gct3, |
| FloatRegister gcvt1, |
| FloatRegister gcvt2, |
| FloatRegister gcvt3) |
| : _bs_asm(BarrierSet::barrier_set()->barrier_set_assembler()), |
| _masm(masm), |
| _decorators(decorators), |
| _type(type), |
| _gct1(gct1), |
| _gct2(gct2), |
| _gct3(gct3), |
| _gcvt1(gcvt1), |
| _gcvt2(gcvt2), |
| _gcvt3(gcvt3) { |
| } |
| |
| void copy_load_at_32(FloatRegister dst1, FloatRegister dst2, Address src) { |
| _bs_asm->copy_load_at(_masm, _decorators, _type, 32, |
| dst1, dst2, src, |
| _gct1, _gct2, _gcvt1); |
| } |
| |
| void copy_store_at_32(Address dst, FloatRegister src1, FloatRegister src2) { |
| _bs_asm->copy_store_at(_masm, _decorators, _type, 32, |
| dst, src1, src2, |
| _gct1, _gct2, _gct3, _gcvt1, _gcvt2, _gcvt3); |
| } |
| |
| void copy_load_at_16(Register dst1, Register dst2, Address src) { |
| _bs_asm->copy_load_at(_masm, _decorators, _type, 16, |
| dst1, dst2, src, |
| _gct1); |
| } |
| |
| void copy_store_at_16(Address dst, Register src1, Register src2) { |
| _bs_asm->copy_store_at(_masm, _decorators, _type, 16, |
| dst, src1, src2, |
| _gct1, _gct2, _gct3); |
| } |
| |
| void copy_load_at_8(Register dst, Address src) { |
| _bs_asm->copy_load_at(_masm, _decorators, _type, 8, |
| dst, noreg, src, |
| _gct1); |
| } |
| |
| void copy_store_at_8(Address dst, Register src) { |
| _bs_asm->copy_store_at(_masm, _decorators, _type, 8, |
| dst, src, noreg, |
| _gct1, _gct2, _gct3); |
| } |
| }; |
| |
| // Bulk copy of blocks of 8 words. |
| // |
| // count is a count of words. |
| // |
| // Precondition: count >= 8 |
| // |
| // Postconditions: |
| // |
| // The least significant bit of count contains the remaining count |
| // of words to copy. The rest of count is trash. |
| // |
| // s and d are adjusted to point to the remaining words to copy |
| // |
| void generate_copy_longs(StubGenStubId stub_id, DecoratorSet decorators, Label &start, Register s, Register d, Register count) { |
| BasicType type; |
| copy_direction direction; |
| |
| switch (stub_id) { |
| case copy_byte_f_id: |
| direction = copy_forwards; |
| type = T_BYTE; |
| break; |
| case copy_byte_b_id: |
| direction = copy_backwards; |
| type = T_BYTE; |
| break; |
| case copy_oop_f_id: |
| direction = copy_forwards; |
| type = T_OBJECT; |
| break; |
| case copy_oop_b_id: |
| direction = copy_backwards; |
| type = T_OBJECT; |
| break; |
| case copy_oop_uninit_f_id: |
| direction = copy_forwards; |
| type = T_OBJECT; |
| break; |
| case copy_oop_uninit_b_id: |
| direction = copy_backwards; |
| type = T_OBJECT; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| int unit = wordSize * direction; |
| int bias = (UseSIMDForMemoryOps ? 4:2) * wordSize; |
| |
| const Register t0 = r3, t1 = r4, t2 = r5, t3 = r6, |
| t4 = r7, t5 = r11, t6 = r12, t7 = r13; |
| const Register stride = r14; |
| const Register gct1 = rscratch1, gct2 = rscratch2, gct3 = r10; |
| const FloatRegister gcvt1 = v6, gcvt2 = v7, gcvt3 = v16; // Note that v8-v15 are callee saved |
| ArrayCopyBarrierSetHelper bs(_masm, decorators, type, gct1, gct2, gct3, gcvt1, gcvt2, gcvt3); |
| |
| assert_different_registers(rscratch1, rscratch2, t0, t1, t2, t3, t4, t5, t6, t7); |
| assert_different_registers(s, d, count, rscratch1, rscratch2); |
| |
| Label again, drain; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| |
| __ bind(start); |
| |
| Label unaligned_copy_long; |
| if (AvoidUnalignedAccesses) { |
| __ tbnz(d, 3, unaligned_copy_long); |
| } |
| |
| if (direction == copy_forwards) { |
| __ sub(s, s, bias); |
| __ sub(d, d, bias); |
| } |
| |
| #ifdef ASSERT |
| // Make sure we are never given < 8 words |
| { |
| Label L; |
| __ cmp(count, (u1)8); |
| __ br(Assembler::GE, L); |
| __ stop("genrate_copy_longs called with < 8 words"); |
| __ bind(L); |
| } |
| #endif |
| |
| // Fill 8 registers |
| if (UseSIMDForMemoryOps) { |
| bs.copy_load_at_32(v0, v1, Address(s, 4 * unit)); |
| bs.copy_load_at_32(v2, v3, Address(__ pre(s, 8 * unit))); |
| } else { |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_load_at_16(t2, t3, Address(s, 4 * unit)); |
| bs.copy_load_at_16(t4, t5, Address(s, 6 * unit)); |
| bs.copy_load_at_16(t6, t7, Address(__ pre(s, 8 * unit))); |
| } |
| |
| __ subs(count, count, 16); |
| __ br(Assembler::LO, drain); |
| |
| int prefetch = PrefetchCopyIntervalInBytes; |
| bool use_stride = false; |
| if (direction == copy_backwards) { |
| use_stride = prefetch > 256; |
| prefetch = -prefetch; |
| if (use_stride) __ mov(stride, prefetch); |
| } |
| |
| __ bind(again); |
| |
| if (PrefetchCopyIntervalInBytes > 0) |
| __ prfm(use_stride ? Address(s, stride) : Address(s, prefetch), PLDL1KEEP); |
| |
| if (UseSIMDForMemoryOps) { |
| bs.copy_store_at_32(Address(d, 4 * unit), v0, v1); |
| bs.copy_load_at_32(v0, v1, Address(s, 4 * unit)); |
| bs.copy_store_at_32(Address(__ pre(d, 8 * unit)), v2, v3); |
| bs.copy_load_at_32(v2, v3, Address(__ pre(s, 8 * unit))); |
| } else { |
| bs.copy_store_at_16(Address(d, 2 * unit), t0, t1); |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_store_at_16(Address(d, 4 * unit), t2, t3); |
| bs.copy_load_at_16(t2, t3, Address(s, 4 * unit)); |
| bs.copy_store_at_16(Address(d, 6 * unit), t4, t5); |
| bs.copy_load_at_16(t4, t5, Address(s, 6 * unit)); |
| bs.copy_store_at_16(Address(__ pre(d, 8 * unit)), t6, t7); |
| bs.copy_load_at_16(t6, t7, Address(__ pre(s, 8 * unit))); |
| } |
| |
| __ subs(count, count, 8); |
| __ br(Assembler::HS, again); |
| |
| // Drain |
| __ bind(drain); |
| if (UseSIMDForMemoryOps) { |
| bs.copy_store_at_32(Address(d, 4 * unit), v0, v1); |
| bs.copy_store_at_32(Address(__ pre(d, 8 * unit)), v2, v3); |
| } else { |
| bs.copy_store_at_16(Address(d, 2 * unit), t0, t1); |
| bs.copy_store_at_16(Address(d, 4 * unit), t2, t3); |
| bs.copy_store_at_16(Address(d, 6 * unit), t4, t5); |
| bs.copy_store_at_16(Address(__ pre(d, 8 * unit)), t6, t7); |
| } |
| |
| { |
| Label L1, L2; |
| __ tbz(count, exact_log2(4), L1); |
| if (UseSIMDForMemoryOps) { |
| bs.copy_load_at_32(v0, v1, Address(__ pre(s, 4 * unit))); |
| bs.copy_store_at_32(Address(__ pre(d, 4 * unit)), v0, v1); |
| } else { |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_load_at_16(t2, t3, Address(__ pre(s, 4 * unit))); |
| bs.copy_store_at_16(Address(d, 2 * unit), t0, t1); |
| bs.copy_store_at_16(Address(__ pre(d, 4 * unit)), t2, t3); |
| } |
| __ bind(L1); |
| |
| if (direction == copy_forwards) { |
| __ add(s, s, bias); |
| __ add(d, d, bias); |
| } |
| |
| __ tbz(count, 1, L2); |
| bs.copy_load_at_16(t0, t1, Address(__ adjust(s, 2 * unit, direction == copy_backwards))); |
| bs.copy_store_at_16(Address(__ adjust(d, 2 * unit, direction == copy_backwards)), t0, t1); |
| __ bind(L2); |
| } |
| |
| __ ret(lr); |
| |
| if (AvoidUnalignedAccesses) { |
| Label drain, again; |
| // Register order for storing. Order is different for backward copy. |
| |
| __ bind(unaligned_copy_long); |
| |
| // source address is even aligned, target odd aligned |
| // |
| // when forward copying word pairs we read long pairs at offsets |
| // {0, 2, 4, 6} (in long words). when backwards copying we read |
| // long pairs at offsets {-2, -4, -6, -8}. We adjust the source |
| // address by -2 in the forwards case so we can compute the |
| // source offsets for both as {2, 4, 6, 8} * unit where unit = 1 |
| // or -1. |
| // |
| // when forward copying we need to store 1 word, 3 pairs and |
| // then 1 word at offsets {0, 1, 3, 5, 7}. Rather than use a |
| // zero offset We adjust the destination by -1 which means we |
| // have to use offsets { 1, 2, 4, 6, 8} * unit for the stores. |
| // |
| // When backwards copyng we need to store 1 word, 3 pairs and |
| // then 1 word at offsets {-1, -3, -5, -7, -8} i.e. we use |
| // offsets {1, 3, 5, 7, 8} * unit. |
| |
| if (direction == copy_forwards) { |
| __ sub(s, s, 16); |
| __ sub(d, d, 8); |
| } |
| |
| // Fill 8 registers |
| // |
| // for forwards copy s was offset by -16 from the original input |
| // value of s so the register contents are at these offsets |
| // relative to the 64 bit block addressed by that original input |
| // and so on for each successive 64 byte block when s is updated |
| // |
| // t0 at offset 0, t1 at offset 8 |
| // t2 at offset 16, t3 at offset 24 |
| // t4 at offset 32, t5 at offset 40 |
| // t6 at offset 48, t7 at offset 56 |
| |
| // for backwards copy s was not offset so the register contents |
| // are at these offsets into the preceding 64 byte block |
| // relative to that original input and so on for each successive |
| // preceding 64 byte block when s is updated. this explains the |
| // slightly counter-intuitive looking pattern of register usage |
| // in the stp instructions for backwards copy. |
| // |
| // t0 at offset -16, t1 at offset -8 |
| // t2 at offset -32, t3 at offset -24 |
| // t4 at offset -48, t5 at offset -40 |
| // t6 at offset -64, t7 at offset -56 |
| |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_load_at_16(t2, t3, Address(s, 4 * unit)); |
| bs.copy_load_at_16(t4, t5, Address(s, 6 * unit)); |
| bs.copy_load_at_16(t6, t7, Address(__ pre(s, 8 * unit))); |
| |
| __ subs(count, count, 16); |
| __ br(Assembler::LO, drain); |
| |
| int prefetch = PrefetchCopyIntervalInBytes; |
| bool use_stride = false; |
| if (direction == copy_backwards) { |
| use_stride = prefetch > 256; |
| prefetch = -prefetch; |
| if (use_stride) __ mov(stride, prefetch); |
| } |
| |
| __ bind(again); |
| |
| if (PrefetchCopyIntervalInBytes > 0) |
| __ prfm(use_stride ? Address(s, stride) : Address(s, prefetch), PLDL1KEEP); |
| |
| if (direction == copy_forwards) { |
| // allowing for the offset of -8 the store instructions place |
| // registers into the target 64 bit block at the following |
| // offsets |
| // |
| // t0 at offset 0 |
| // t1 at offset 8, t2 at offset 16 |
| // t3 at offset 24, t4 at offset 32 |
| // t5 at offset 40, t6 at offset 48 |
| // t7 at offset 56 |
| |
| bs.copy_store_at_8(Address(d, 1 * unit), t0); |
| bs.copy_store_at_16(Address(d, 2 * unit), t1, t2); |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_store_at_16(Address(d, 4 * unit), t3, t4); |
| bs.copy_load_at_16(t2, t3, Address(s, 4 * unit)); |
| bs.copy_store_at_16(Address(d, 6 * unit), t5, t6); |
| bs.copy_load_at_16(t4, t5, Address(s, 6 * unit)); |
| bs.copy_store_at_8(Address(__ pre(d, 8 * unit)), t7); |
| bs.copy_load_at_16(t6, t7, Address(__ pre(s, 8 * unit))); |
| } else { |
| // d was not offset when we started so the registers are |
| // written into the 64 bit block preceding d with the following |
| // offsets |
| // |
| // t1 at offset -8 |
| // t3 at offset -24, t0 at offset -16 |
| // t5 at offset -48, t2 at offset -32 |
| // t7 at offset -56, t4 at offset -48 |
| // t6 at offset -64 |
| // |
| // note that this matches the offsets previously noted for the |
| // loads |
| |
| bs.copy_store_at_8(Address(d, 1 * unit), t1); |
| bs.copy_store_at_16(Address(d, 3 * unit), t3, t0); |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_store_at_16(Address(d, 5 * unit), t5, t2); |
| bs.copy_load_at_16(t2, t3, Address(s, 4 * unit)); |
| bs.copy_store_at_16(Address(d, 7 * unit), t7, t4); |
| bs.copy_load_at_16(t4, t5, Address(s, 6 * unit)); |
| bs.copy_store_at_8(Address(__ pre(d, 8 * unit)), t6); |
| bs.copy_load_at_16(t6, t7, Address(__ pre(s, 8 * unit))); |
| } |
| |
| __ subs(count, count, 8); |
| __ br(Assembler::HS, again); |
| |
| // Drain |
| // |
| // this uses the same pattern of offsets and register arguments |
| // as above |
| __ bind(drain); |
| if (direction == copy_forwards) { |
| bs.copy_store_at_8(Address(d, 1 * unit), t0); |
| bs.copy_store_at_16(Address(d, 2 * unit), t1, t2); |
| bs.copy_store_at_16(Address(d, 4 * unit), t3, t4); |
| bs.copy_store_at_16(Address(d, 6 * unit), t5, t6); |
| bs.copy_store_at_8(Address(__ pre(d, 8 * unit)), t7); |
| } else { |
| bs.copy_store_at_8(Address(d, 1 * unit), t1); |
| bs.copy_store_at_16(Address(d, 3 * unit), t3, t0); |
| bs.copy_store_at_16(Address(d, 5 * unit), t5, t2); |
| bs.copy_store_at_16(Address(d, 7 * unit), t7, t4); |
| bs.copy_store_at_8(Address(__ pre(d, 8 * unit)), t6); |
| } |
| // now we need to copy any remaining part block which may |
| // include a 4 word block subblock and/or a 2 word subblock. |
| // bits 2 and 1 in the count are the tell-tale for whether we |
| // have each such subblock |
| { |
| Label L1, L2; |
| __ tbz(count, exact_log2(4), L1); |
| // this is the same as above but copying only 4 longs hence |
| // with only one intervening stp between the str instructions |
| // but note that the offsets and registers still follow the |
| // same pattern |
| bs.copy_load_at_16(t0, t1, Address(s, 2 * unit)); |
| bs.copy_load_at_16(t2, t3, Address(__ pre(s, 4 * unit))); |
| if (direction == copy_forwards) { |
| bs.copy_store_at_8(Address(d, 1 * unit), t0); |
| bs.copy_store_at_16(Address(d, 2 * unit), t1, t2); |
| bs.copy_store_at_8(Address(__ pre(d, 4 * unit)), t3); |
| } else { |
| bs.copy_store_at_8(Address(d, 1 * unit), t1); |
| bs.copy_store_at_16(Address(d, 3 * unit), t3, t0); |
| bs.copy_store_at_8(Address(__ pre(d, 4 * unit)), t2); |
| } |
| __ bind(L1); |
| |
| __ tbz(count, 1, L2); |
| // this is the same as above but copying only 2 longs hence |
| // there is no intervening stp between the str instructions |
| // but note that the offset and register patterns are still |
| // the same |
| bs.copy_load_at_16(t0, t1, Address(__ pre(s, 2 * unit))); |
| if (direction == copy_forwards) { |
| bs.copy_store_at_8(Address(d, 1 * unit), t0); |
| bs.copy_store_at_8(Address(__ pre(d, 2 * unit)), t1); |
| } else { |
| bs.copy_store_at_8(Address(d, 1 * unit), t1); |
| bs.copy_store_at_8(Address(__ pre(d, 2 * unit)), t0); |
| } |
| __ bind(L2); |
| |
| // for forwards copy we need to re-adjust the offsets we |
| // applied so that s and d are follow the last words written |
| |
| if (direction == copy_forwards) { |
| __ add(s, s, 16); |
| __ add(d, d, 8); |
| } |
| |
| } |
| |
| __ ret(lr); |
| } |
| } |
| |
| // Small copy: less than 16 bytes. |
| // |
| // NB: Ignores all of the bits of count which represent more than 15 |
| // bytes, so a caller doesn't have to mask them. |
| |
| void copy_memory_small(DecoratorSet decorators, BasicType type, Register s, Register d, Register count, int step) { |
| bool is_backwards = step < 0; |
| size_t granularity = g_uabs(step); |
| int direction = is_backwards ? -1 : 1; |
| |
| Label Lword, Lint, Lshort, Lbyte; |
| |
| assert(granularity |
| && granularity <= sizeof (jlong), "Impossible granularity in copy_memory_small"); |
| |
| const Register t0 = r3; |
| const Register gct1 = rscratch1, gct2 = rscratch2, gct3 = r10; |
| ArrayCopyBarrierSetHelper bs(_masm, decorators, type, gct1, gct2, gct3, fnoreg, fnoreg, fnoreg); |
| |
| // ??? I don't know if this bit-test-and-branch is the right thing |
| // to do. It does a lot of jumping, resulting in several |
| // mispredicted branches. It might make more sense to do this |
| // with something like Duff's device with a single computed branch. |
| |
| __ tbz(count, 3 - exact_log2(granularity), Lword); |
| bs.copy_load_at_8(t0, Address(__ adjust(s, direction * wordSize, is_backwards))); |
| bs.copy_store_at_8(Address(__ adjust(d, direction * wordSize, is_backwards)), t0); |
| __ bind(Lword); |
| |
| if (granularity <= sizeof (jint)) { |
| __ tbz(count, 2 - exact_log2(granularity), Lint); |
| __ ldrw(t0, Address(__ adjust(s, sizeof (jint) * direction, is_backwards))); |
| __ strw(t0, Address(__ adjust(d, sizeof (jint) * direction, is_backwards))); |
| __ bind(Lint); |
| } |
| |
| if (granularity <= sizeof (jshort)) { |
| __ tbz(count, 1 - exact_log2(granularity), Lshort); |
| __ ldrh(t0, Address(__ adjust(s, sizeof (jshort) * direction, is_backwards))); |
| __ strh(t0, Address(__ adjust(d, sizeof (jshort) * direction, is_backwards))); |
| __ bind(Lshort); |
| } |
| |
| if (granularity <= sizeof (jbyte)) { |
| __ tbz(count, 0, Lbyte); |
| __ ldrb(t0, Address(__ adjust(s, sizeof (jbyte) * direction, is_backwards))); |
| __ strb(t0, Address(__ adjust(d, sizeof (jbyte) * direction, is_backwards))); |
| __ bind(Lbyte); |
| } |
| } |
| |
| Label copy_f, copy_b; |
| Label copy_obj_f, copy_obj_b; |
| Label copy_obj_uninit_f, copy_obj_uninit_b; |
| |
| // All-singing all-dancing memory copy. |
| // |
| // Copy count units of memory from s to d. The size of a unit is |
| // step, which can be positive or negative depending on the direction |
| // of copy. If is_aligned is false, we align the source address. |
| // |
| |
| void copy_memory(DecoratorSet decorators, BasicType type, bool is_aligned, |
| Register s, Register d, Register count, int step) { |
| copy_direction direction = step < 0 ? copy_backwards : copy_forwards; |
| bool is_backwards = step < 0; |
| unsigned int granularity = g_uabs(step); |
| const Register t0 = r3, t1 = r4; |
| |
| // <= 80 (or 96 for SIMD) bytes do inline. Direction doesn't matter because we always |
| // load all the data before writing anything |
| Label copy4, copy8, copy16, copy32, copy80, copy_big, finish; |
| const Register t2 = r5, t3 = r6, t4 = r7, t5 = r11; |
| const Register t6 = r12, t7 = r13, t8 = r14, t9 = r15; |
| const Register send = r17, dend = r16; |
| const Register gct1 = rscratch1, gct2 = rscratch2, gct3 = r10; |
| const FloatRegister gcvt1 = v6, gcvt2 = v7, gcvt3 = v16; // Note that v8-v15 are callee saved |
| ArrayCopyBarrierSetHelper bs(_masm, decorators, type, gct1, gct2, gct3, gcvt1, gcvt2, gcvt3); |
| |
| if (PrefetchCopyIntervalInBytes > 0) |
| __ prfm(Address(s, 0), PLDL1KEEP); |
| __ cmp(count, u1((UseSIMDForMemoryOps ? 96:80)/granularity)); |
| __ br(Assembler::HI, copy_big); |
| |
| __ lea(send, Address(s, count, Address::lsl(exact_log2(granularity)))); |
| __ lea(dend, Address(d, count, Address::lsl(exact_log2(granularity)))); |
| |
| __ cmp(count, u1(16/granularity)); |
| __ br(Assembler::LS, copy16); |
| |
| __ cmp(count, u1(64/granularity)); |
| __ br(Assembler::HI, copy80); |
| |
| __ cmp(count, u1(32/granularity)); |
| __ br(Assembler::LS, copy32); |
| |
| // 33..64 bytes |
| if (UseSIMDForMemoryOps) { |
| bs.copy_load_at_32(v0, v1, Address(s, 0)); |
| bs.copy_load_at_32(v2, v3, Address(send, -32)); |
| bs.copy_store_at_32(Address(d, 0), v0, v1); |
| bs.copy_store_at_32(Address(dend, -32), v2, v3); |
| } else { |
| bs.copy_load_at_16(t0, t1, Address(s, 0)); |
| bs.copy_load_at_16(t2, t3, Address(s, 16)); |
| bs.copy_load_at_16(t4, t5, Address(send, -32)); |
| bs.copy_load_at_16(t6, t7, Address(send, -16)); |
| |
| bs.copy_store_at_16(Address(d, 0), t0, t1); |
| bs.copy_store_at_16(Address(d, 16), t2, t3); |
| bs.copy_store_at_16(Address(dend, -32), t4, t5); |
| bs.copy_store_at_16(Address(dend, -16), t6, t7); |
| } |
| __ b(finish); |
| |
| // 17..32 bytes |
| __ bind(copy32); |
| bs.copy_load_at_16(t0, t1, Address(s, 0)); |
| bs.copy_load_at_16(t6, t7, Address(send, -16)); |
| |
| bs.copy_store_at_16(Address(d, 0), t0, t1); |
| bs.copy_store_at_16(Address(dend, -16), t6, t7); |
| __ b(finish); |
| |
| // 65..80/96 bytes |
| // (96 bytes if SIMD because we do 32 byes per instruction) |
| __ bind(copy80); |
| if (UseSIMDForMemoryOps) { |
| bs.copy_load_at_32(v0, v1, Address(s, 0)); |
| bs.copy_load_at_32(v2, v3, Address(s, 32)); |
| // Unaligned pointers can be an issue for copying. |
| // The issue has more chances to happen when granularity of data is |
| // less than 4(sizeof(jint)). Pointers for arrays of jint are at least |
| // 4 byte aligned. Pointers for arrays of jlong are 8 byte aligned. |
| // The most performance drop has been seen for the range 65-80 bytes. |
| // For such cases using the pair of ldp/stp instead of the third pair of |
| // ldpq/stpq fixes the performance issue. |
| if (granularity < sizeof (jint)) { |
| Label copy96; |
| __ cmp(count, u1(80/granularity)); |
| __ br(Assembler::HI, copy96); |
| bs.copy_load_at_16(t0, t1, Address(send, -16)); |
| |
| bs.copy_store_at_32(Address(d, 0), v0, v1); |
| bs.copy_store_at_32(Address(d, 32), v2, v3); |
| |
| bs.copy_store_at_16(Address(dend, -16), t0, t1); |
| __ b(finish); |
| |
| __ bind(copy96); |
| } |
| bs.copy_load_at_32(v4, v5, Address(send, -32)); |
| |
| bs.copy_store_at_32(Address(d, 0), v0, v1); |
| bs.copy_store_at_32(Address(d, 32), v2, v3); |
| |
| bs.copy_store_at_32(Address(dend, -32), v4, v5); |
| } else { |
| bs.copy_load_at_16(t0, t1, Address(s, 0)); |
| bs.copy_load_at_16(t2, t3, Address(s, 16)); |
| bs.copy_load_at_16(t4, t5, Address(s, 32)); |
| bs.copy_load_at_16(t6, t7, Address(s, 48)); |
| bs.copy_load_at_16(t8, t9, Address(send, -16)); |
| |
| bs.copy_store_at_16(Address(d, 0), t0, t1); |
| bs.copy_store_at_16(Address(d, 16), t2, t3); |
| bs.copy_store_at_16(Address(d, 32), t4, t5); |
| bs.copy_store_at_16(Address(d, 48), t6, t7); |
| bs.copy_store_at_16(Address(dend, -16), t8, t9); |
| } |
| __ b(finish); |
| |
| // 0..16 bytes |
| __ bind(copy16); |
| __ cmp(count, u1(8/granularity)); |
| __ br(Assembler::LO, copy8); |
| |
| // 8..16 bytes |
| bs.copy_load_at_8(t0, Address(s, 0)); |
| bs.copy_load_at_8(t1, Address(send, -8)); |
| bs.copy_store_at_8(Address(d, 0), t0); |
| bs.copy_store_at_8(Address(dend, -8), t1); |
| __ b(finish); |
| |
| if (granularity < 8) { |
| // 4..7 bytes |
| __ bind(copy8); |
| __ tbz(count, 2 - exact_log2(granularity), copy4); |
| __ ldrw(t0, Address(s, 0)); |
| __ ldrw(t1, Address(send, -4)); |
| __ strw(t0, Address(d, 0)); |
| __ strw(t1, Address(dend, -4)); |
| __ b(finish); |
| if (granularity < 4) { |
| // 0..3 bytes |
| __ bind(copy4); |
| __ cbz(count, finish); // get rid of 0 case |
| if (granularity == 2) { |
| __ ldrh(t0, Address(s, 0)); |
| __ strh(t0, Address(d, 0)); |
| } else { // granularity == 1 |
| // Now 1..3 bytes. Handle the 1 and 2 byte case by copying |
| // the first and last byte. |
| // Handle the 3 byte case by loading and storing base + count/2 |
| // (count == 1 (s+0)->(d+0), count == 2,3 (s+1) -> (d+1)) |
| // This does means in the 1 byte case we load/store the same |
| // byte 3 times. |
| __ lsr(count, count, 1); |
| __ ldrb(t0, Address(s, 0)); |
| __ ldrb(t1, Address(send, -1)); |
| __ ldrb(t2, Address(s, count)); |
| __ strb(t0, Address(d, 0)); |
| __ strb(t1, Address(dend, -1)); |
| __ strb(t2, Address(d, count)); |
| } |
| __ b(finish); |
| } |
| } |
| |
| __ bind(copy_big); |
| if (is_backwards) { |
| __ lea(s, Address(s, count, Address::lsl(exact_log2(-step)))); |
| __ lea(d, Address(d, count, Address::lsl(exact_log2(-step)))); |
| } |
| |
| // Now we've got the small case out of the way we can align the |
| // source address on a 2-word boundary. |
| |
| // Here we will materialize a count in r15, which is used by copy_memory_small |
| // and the various generate_copy_longs stubs that we use for 2 word aligned bytes. |
| // Up until here, we have used t9, which aliases r15, but from here on, that register |
| // can not be used as a temp register, as it contains the count. |
| |
| Label aligned; |
| |
| if (is_aligned) { |
| // We may have to adjust by 1 word to get s 2-word-aligned. |
| __ tbz(s, exact_log2(wordSize), aligned); |
| bs.copy_load_at_8(t0, Address(__ adjust(s, direction * wordSize, is_backwards))); |
| bs.copy_store_at_8(Address(__ adjust(d, direction * wordSize, is_backwards)), t0); |
| __ sub(count, count, wordSize/granularity); |
| } else { |
| if (is_backwards) { |
| __ andr(r15, s, 2 * wordSize - 1); |
| } else { |
| __ neg(r15, s); |
| __ andr(r15, r15, 2 * wordSize - 1); |
| } |
| // r15 is the byte adjustment needed to align s. |
| __ cbz(r15, aligned); |
| int shift = exact_log2(granularity); |
| if (shift > 0) { |
| __ lsr(r15, r15, shift); |
| } |
| __ sub(count, count, r15); |
| |
| #if 0 |
| // ?? This code is only correct for a disjoint copy. It may or |
| // may not make sense to use it in that case. |
| |
| // Copy the first pair; s and d may not be aligned. |
| __ ldp(t0, t1, Address(s, is_backwards ? -2 * wordSize : 0)); |
| __ stp(t0, t1, Address(d, is_backwards ? -2 * wordSize : 0)); |
| |
| // Align s and d, adjust count |
| if (is_backwards) { |
| __ sub(s, s, r15); |
| __ sub(d, d, r15); |
| } else { |
| __ add(s, s, r15); |
| __ add(d, d, r15); |
| } |
| #else |
| copy_memory_small(decorators, type, s, d, r15, step); |
| #endif |
| } |
| |
| __ bind(aligned); |
| |
| // s is now 2-word-aligned. |
| |
| // We have a count of units and some trailing bytes. Adjust the |
| // count and do a bulk copy of words. If the shift is zero |
| // perform a move instead to benefit from zero latency moves. |
| int shift = exact_log2(wordSize/granularity); |
| if (shift > 0) { |
| __ lsr(r15, count, shift); |
| } else { |
| __ mov(r15, count); |
| } |
| if (direction == copy_forwards) { |
| if (type != T_OBJECT) { |
| __ bl(copy_f); |
| } else if ((decorators & IS_DEST_UNINITIALIZED) != 0) { |
| __ bl(copy_obj_uninit_f); |
| } else { |
| __ bl(copy_obj_f); |
| } |
| } else { |
| if (type != T_OBJECT) { |
| __ bl(copy_b); |
| } else if ((decorators & IS_DEST_UNINITIALIZED) != 0) { |
| __ bl(copy_obj_uninit_b); |
| } else { |
| __ bl(copy_obj_b); |
| } |
| } |
| |
| // And the tail. |
| copy_memory_small(decorators, type, s, d, count, step); |
| |
| if (granularity >= 8) __ bind(copy8); |
| if (granularity >= 4) __ bind(copy4); |
| __ bind(finish); |
| } |
| |
| |
| void clobber_registers() { |
| #ifdef ASSERT |
| RegSet clobbered |
| = MacroAssembler::call_clobbered_gp_registers() - rscratch1; |
| __ mov(rscratch1, (uint64_t)0xdeadbeef); |
| __ orr(rscratch1, rscratch1, rscratch1, Assembler::LSL, 32); |
| for (RegSetIterator<Register> it = clobbered.begin(); *it != noreg; ++it) { |
| __ mov(*it, rscratch1); |
| } |
| #endif |
| |
| } |
| |
| // Scan over array at a for count oops, verifying each one. |
| // Preserves a and count, clobbers rscratch1 and rscratch2. |
| void verify_oop_array (int size, Register a, Register count, Register temp) { |
| Label loop, end; |
| __ mov(rscratch1, a); |
| __ mov(rscratch2, zr); |
| __ bind(loop); |
| __ cmp(rscratch2, count); |
| __ br(Assembler::HS, end); |
| if (size == wordSize) { |
| __ ldr(temp, Address(a, rscratch2, Address::lsl(exact_log2(size)))); |
| __ verify_oop(temp); |
| } else { |
| __ ldrw(temp, Address(a, rscratch2, Address::lsl(exact_log2(size)))); |
| __ decode_heap_oop(temp); // calls verify_oop |
| } |
| __ add(rscratch2, rscratch2, 1); |
| __ b(loop); |
| __ bind(end); |
| } |
| |
| // Arguments: |
| // stub_id - is used to name the stub and identify all details of |
| // how to perform the copy. |
| // |
| // entry - is assigned to the stub's post push entry point unless |
| // it is null |
| // |
| // Inputs: |
| // c_rarg0 - source array address |
| // c_rarg1 - destination array address |
| // c_rarg2 - element count, treated as ssize_t, can be zero |
| // |
| // If 'from' and/or 'to' are aligned on 4-byte boundaries, we let |
| // the hardware handle it. The two dwords within qwords that span |
| // cache line boundaries will still be loaded and stored atomically. |
| // |
| // Side Effects: entry is set to the (post push) entry point so it |
| // can be used by the corresponding conjoint copy |
| // method |
| // |
| address generate_disjoint_copy(StubGenStubId stub_id, address *entry) { |
| Register s = c_rarg0, d = c_rarg1, count = c_rarg2; |
| RegSet saved_reg = RegSet::of(s, d, count); |
| int size; |
| bool aligned; |
| bool is_oop; |
| bool dest_uninitialized; |
| switch (stub_id) { |
| case jbyte_disjoint_arraycopy_id: |
| size = sizeof(jbyte); |
| aligned = false; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case arrayof_jbyte_disjoint_arraycopy_id: |
| size = sizeof(jbyte); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case jshort_disjoint_arraycopy_id: |
| size = sizeof(jshort); |
| aligned = false; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case arrayof_jshort_disjoint_arraycopy_id: |
| size = sizeof(jshort); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case jint_disjoint_arraycopy_id: |
| size = sizeof(jint); |
| aligned = false; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case arrayof_jint_disjoint_arraycopy_id: |
| size = sizeof(jint); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case jlong_disjoint_arraycopy_id: |
| // since this is always aligned we can (should!) use the same |
| // stub as for case arrayof_jlong_disjoint_arraycopy |
| ShouldNotReachHere(); |
| break; |
| case arrayof_jlong_disjoint_arraycopy_id: |
| size = sizeof(jlong); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case oop_disjoint_arraycopy_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = false; |
| break; |
| case arrayof_oop_disjoint_arraycopy_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = false; |
| break; |
| case oop_disjoint_arraycopy_uninit_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = true; |
| break; |
| case arrayof_oop_disjoint_arraycopy_uninit_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| break; |
| } |
| |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| if (entry != nullptr) { |
| *entry = __ pc(); |
| // caller can pass a 64-bit byte count here (from Unsafe.copyMemory) |
| BLOCK_COMMENT("Entry:"); |
| } |
| |
| DecoratorSet decorators = IN_HEAP | IS_ARRAY | ARRAYCOPY_DISJOINT; |
| if (dest_uninitialized) { |
| decorators |= IS_DEST_UNINITIALIZED; |
| } |
| if (aligned) { |
| decorators |= ARRAYCOPY_ALIGNED; |
| } |
| |
| BarrierSetAssembler *bs = BarrierSet::barrier_set()->barrier_set_assembler(); |
| bs->arraycopy_prologue(_masm, decorators, is_oop, s, d, count, saved_reg); |
| |
| if (is_oop) { |
| // save regs before copy_memory |
| __ push(RegSet::of(d, count), sp); |
| } |
| { |
| // UnsafeMemoryAccess page error: continue after unsafe access |
| bool add_entry = !is_oop && (!aligned || sizeof(jlong) == size); |
| UnsafeMemoryAccessMark umam(this, add_entry, true); |
| copy_memory(decorators, is_oop ? T_OBJECT : T_BYTE, aligned, s, d, count, size); |
| } |
| |
| if (is_oop) { |
| __ pop(RegSet::of(d, count), sp); |
| if (VerifyOops) |
| verify_oop_array(size, d, count, r16); |
| } |
| |
| bs->arraycopy_epilogue(_masm, decorators, is_oop, d, count, rscratch1, RegSet()); |
| |
| __ leave(); |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| return start; |
| } |
| |
| // Arguments: |
| // stub_id - is used to name the stub and identify all details of |
| // how to perform the copy. |
| // |
| // nooverlap_target - identifes the (post push) entry for the |
| // corresponding disjoint copy routine which can be |
| // jumped to if the ranges do not actually overlap |
| // |
| // entry - is assigned to the stub's post push entry point unless |
| // it is null |
| // |
| // |
| // Inputs: |
| // c_rarg0 - source array address |
| // c_rarg1 - destination array address |
| // c_rarg2 - element count, treated as ssize_t, can be zero |
| // |
| // If 'from' and/or 'to' are aligned on 4-byte boundaries, we let |
| // the hardware handle it. The two dwords within qwords that span |
| // cache line boundaries will still be loaded and stored atomically. |
| // |
| // Side Effects: |
| // entry is set to the no-overlap entry point so it can be used by |
| // some other conjoint copy method |
| // |
| address generate_conjoint_copy(StubGenStubId stub_id, address nooverlap_target, address *entry) { |
| Register s = c_rarg0, d = c_rarg1, count = c_rarg2; |
| RegSet saved_regs = RegSet::of(s, d, count); |
| int size; |
| bool aligned; |
| bool is_oop; |
| bool dest_uninitialized; |
| switch (stub_id) { |
| case jbyte_arraycopy_id: |
| size = sizeof(jbyte); |
| aligned = false; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case arrayof_jbyte_arraycopy_id: |
| size = sizeof(jbyte); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case jshort_arraycopy_id: |
| size = sizeof(jshort); |
| aligned = false; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case arrayof_jshort_arraycopy_id: |
| size = sizeof(jshort); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case jint_arraycopy_id: |
| size = sizeof(jint); |
| aligned = false; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case arrayof_jint_arraycopy_id: |
| size = sizeof(jint); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case jlong_arraycopy_id: |
| // since this is always aligned we can (should!) use the same |
| // stub as for case arrayof_jlong_disjoint_arraycopy |
| ShouldNotReachHere(); |
| break; |
| case arrayof_jlong_arraycopy_id: |
| size = sizeof(jlong); |
| aligned = true; |
| is_oop = false; |
| dest_uninitialized = false; |
| break; |
| case oop_arraycopy_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = false; |
| break; |
| case arrayof_oop_arraycopy_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = false; |
| break; |
| case oop_arraycopy_uninit_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = true; |
| break; |
| case arrayof_oop_arraycopy_uninit_id: |
| size = UseCompressedOops ? sizeof (jint) : sizeof (jlong); |
| aligned = !UseCompressedOops; |
| is_oop = true; |
| dest_uninitialized = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| if (entry != nullptr) { |
| *entry = __ pc(); |
| // caller can pass a 64-bit byte count here (from Unsafe.copyMemory) |
| BLOCK_COMMENT("Entry:"); |
| } |
| |
| // use fwd copy when (d-s) above_equal (count*size) |
| __ sub(rscratch1, d, s); |
| __ cmp(rscratch1, count, Assembler::LSL, exact_log2(size)); |
| __ br(Assembler::HS, nooverlap_target); |
| |
| DecoratorSet decorators = IN_HEAP | IS_ARRAY; |
| if (dest_uninitialized) { |
| decorators |= IS_DEST_UNINITIALIZED; |
| } |
| if (aligned) { |
| decorators |= ARRAYCOPY_ALIGNED; |
| } |
| |
| BarrierSetAssembler *bs = BarrierSet::barrier_set()->barrier_set_assembler(); |
| bs->arraycopy_prologue(_masm, decorators, is_oop, s, d, count, saved_regs); |
| |
| if (is_oop) { |
| // save regs before copy_memory |
| __ push(RegSet::of(d, count), sp); |
| } |
| { |
| // UnsafeMemoryAccess page error: continue after unsafe access |
| bool add_entry = !is_oop && (!aligned || sizeof(jlong) == size); |
| UnsafeMemoryAccessMark umam(this, add_entry, true); |
| copy_memory(decorators, is_oop ? T_OBJECT : T_BYTE, aligned, s, d, count, -size); |
| } |
| if (is_oop) { |
| __ pop(RegSet::of(d, count), sp); |
| if (VerifyOops) |
| verify_oop_array(size, d, count, r16); |
| } |
| bs->arraycopy_epilogue(_masm, decorators, is_oop, d, count, rscratch1, RegSet()); |
| __ leave(); |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| return start; |
| } |
| |
| // Helper for generating a dynamic type check. |
| // Smashes rscratch1, rscratch2. |
| void generate_type_check(Register sub_klass, |
| Register super_check_offset, |
| Register super_klass, |
| Register temp1, |
| Register temp2, |
| Register result, |
| Label& L_success) { |
| assert_different_registers(sub_klass, super_check_offset, super_klass); |
| |
| BLOCK_COMMENT("type_check:"); |
| |
| Label L_miss; |
| |
| __ check_klass_subtype_fast_path(sub_klass, super_klass, noreg, &L_success, &L_miss, nullptr, |
| super_check_offset); |
| __ check_klass_subtype_slow_path(sub_klass, super_klass, temp1, temp2, &L_success, nullptr); |
| |
| // Fall through on failure! |
| __ BIND(L_miss); |
| } |
| |
| // |
| // Generate checkcasting array copy stub |
| // |
| // Input: |
| // c_rarg0 - source array address |
| // c_rarg1 - destination array address |
| // c_rarg2 - element count, treated as ssize_t, can be zero |
| // c_rarg3 - size_t ckoff (super_check_offset) |
| // c_rarg4 - oop ckval (super_klass) |
| // |
| // Output: |
| // r0 == 0 - success |
| // r0 == -1^K - failure, where K is partial transfer count |
| // |
| address generate_checkcast_copy(StubGenStubId stub_id, address *entry) { |
| bool dest_uninitialized; |
| switch (stub_id) { |
| case checkcast_arraycopy_id: |
| dest_uninitialized = false; |
| break; |
| case checkcast_arraycopy_uninit_id: |
| dest_uninitialized = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| Label L_load_element, L_store_element, L_do_card_marks, L_done, L_done_pop; |
| |
| // Input registers (after setup_arg_regs) |
| const Register from = c_rarg0; // source array address |
| const Register to = c_rarg1; // destination array address |
| const Register count = c_rarg2; // elementscount |
| const Register ckoff = c_rarg3; // super_check_offset |
| const Register ckval = c_rarg4; // super_klass |
| |
| RegSet wb_pre_saved_regs = RegSet::range(c_rarg0, c_rarg4); |
| RegSet wb_post_saved_regs = RegSet::of(count); |
| |
| // Registers used as temps (r19, r20, r21, r22 are save-on-entry) |
| const Register copied_oop = r22; // actual oop copied |
| const Register count_save = r21; // orig elementscount |
| const Register start_to = r20; // destination array start address |
| const Register r19_klass = r19; // oop._klass |
| |
| // Registers used as gc temps (r5, r6, r7 are save-on-call) |
| const Register gct1 = r5, gct2 = r6, gct3 = r7; |
| |
| //--------------------------------------------------------------- |
| // Assembler stub will be used for this call to arraycopy |
| // if the two arrays are subtypes of Object[] but the |
| // destination array type is not equal to or a supertype |
| // of the source type. Each element must be separately |
| // checked. |
| |
| assert_different_registers(from, to, count, ckoff, ckval, start_to, |
| copied_oop, r19_klass, count_save); |
| |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| #ifdef ASSERT |
| // caller guarantees that the arrays really are different |
| // otherwise, we would have to make conjoint checks |
| { Label L; |
| __ b(L); // conjoint check not yet implemented |
| __ stop("checkcast_copy within a single array"); |
| __ bind(L); |
| } |
| #endif //ASSERT |
| |
| // Caller of this entry point must set up the argument registers. |
| if (entry != nullptr) { |
| *entry = __ pc(); |
| BLOCK_COMMENT("Entry:"); |
| } |
| |
| // Empty array: Nothing to do. |
| __ cbz(count, L_done); |
| __ push(RegSet::of(r19, r20, r21, r22), sp); |
| |
| #ifdef ASSERT |
| BLOCK_COMMENT("assert consistent ckoff/ckval"); |
| // The ckoff and ckval must be mutually consistent, |
| // even though caller generates both. |
| { Label L; |
| int sco_offset = in_bytes(Klass::super_check_offset_offset()); |
| __ ldrw(start_to, Address(ckval, sco_offset)); |
| __ cmpw(ckoff, start_to); |
| __ br(Assembler::EQ, L); |
| __ stop("super_check_offset inconsistent"); |
| __ bind(L); |
| } |
| #endif //ASSERT |
| |
| DecoratorSet decorators = IN_HEAP | IS_ARRAY | ARRAYCOPY_CHECKCAST | ARRAYCOPY_DISJOINT; |
| bool is_oop = true; |
| int element_size = UseCompressedOops ? 4 : 8; |
| if (dest_uninitialized) { |
| decorators |= IS_DEST_UNINITIALIZED; |
| } |
| |
| BarrierSetAssembler *bs = BarrierSet::barrier_set()->barrier_set_assembler(); |
| bs->arraycopy_prologue(_masm, decorators, is_oop, from, to, count, wb_pre_saved_regs); |
| |
| // save the original count |
| __ mov(count_save, count); |
| |
| // Copy from low to high addresses |
| __ mov(start_to, to); // Save destination array start address |
| __ b(L_load_element); |
| |
| // ======== begin loop ======== |
| // (Loop is rotated; its entry is L_load_element.) |
| // Loop control: |
| // for (; count != 0; count--) { |
| // copied_oop = load_heap_oop(from++); |
| // ... generate_type_check ...; |
| // store_heap_oop(to++, copied_oop); |
| // } |
| __ align(OptoLoopAlignment); |
| |
| __ BIND(L_store_element); |
| bs->copy_store_at(_masm, decorators, T_OBJECT, element_size, |
| __ post(to, element_size), copied_oop, noreg, |
| gct1, gct2, gct3); |
| __ sub(count, count, 1); |
| __ cbz(count, L_do_card_marks); |
| |
| // ======== loop entry is here ======== |
| __ BIND(L_load_element); |
| bs->copy_load_at(_masm, decorators, T_OBJECT, element_size, |
| copied_oop, noreg, __ post(from, element_size), |
| gct1); |
| __ cbz(copied_oop, L_store_element); |
| |
| __ load_klass(r19_klass, copied_oop);// query the object klass |
| |
| BLOCK_COMMENT("type_check:"); |
| generate_type_check(/*sub_klass*/r19_klass, |
| /*super_check_offset*/ckoff, |
| /*super_klass*/ckval, |
| /*r_array_base*/gct1, |
| /*temp2*/gct2, |
| /*result*/r10, L_store_element); |
| |
| // Fall through on failure! |
| |
| // ======== end loop ======== |
| |
| // It was a real error; we must depend on the caller to finish the job. |
| // Register count = remaining oops, count_orig = total oops. |
| // Emit GC store barriers for the oops we have copied and report |
| // their number to the caller. |
| |
| __ subs(count, count_save, count); // K = partially copied oop count |
| __ eon(count, count, zr); // report (-1^K) to caller |
| __ br(Assembler::EQ, L_done_pop); |
| |
| __ BIND(L_do_card_marks); |
| bs->arraycopy_epilogue(_masm, decorators, is_oop, start_to, count_save, rscratch1, wb_post_saved_regs); |
| |
| __ bind(L_done_pop); |
| __ pop(RegSet::of(r19, r20, r21, r22), sp); |
| inc_counter_np(SharedRuntime::_checkcast_array_copy_ctr); |
| |
| __ bind(L_done); |
| __ mov(r0, count); |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Perform range checks on the proposed arraycopy. |
| // Kills temp, but nothing else. |
| // Also, clean the sign bits of src_pos and dst_pos. |
| void arraycopy_range_checks(Register src, // source array oop (c_rarg0) |
| Register src_pos, // source position (c_rarg1) |
| Register dst, // destination array oo (c_rarg2) |
| Register dst_pos, // destination position (c_rarg3) |
| Register length, |
| Register temp, |
| Label& L_failed) { |
| BLOCK_COMMENT("arraycopy_range_checks:"); |
| |
| assert_different_registers(rscratch1, temp); |
| |
| // if (src_pos + length > arrayOop(src)->length()) FAIL; |
| __ ldrw(rscratch1, Address(src, arrayOopDesc::length_offset_in_bytes())); |
| __ addw(temp, length, src_pos); |
| __ cmpw(temp, rscratch1); |
| __ br(Assembler::HI, L_failed); |
| |
| // if (dst_pos + length > arrayOop(dst)->length()) FAIL; |
| __ ldrw(rscratch1, Address(dst, arrayOopDesc::length_offset_in_bytes())); |
| __ addw(temp, length, dst_pos); |
| __ cmpw(temp, rscratch1); |
| __ br(Assembler::HI, L_failed); |
| |
| // Have to clean up high 32 bits of 'src_pos' and 'dst_pos'. |
| __ movw(src_pos, src_pos); |
| __ movw(dst_pos, dst_pos); |
| |
| BLOCK_COMMENT("arraycopy_range_checks done"); |
| } |
| |
| // These stubs get called from some dumb test routine. |
| // I'll write them properly when they're called from |
| // something that's actually doing something. |
| static void fake_arraycopy_stub(address src, address dst, int count) { |
| assert(count == 0, "huh?"); |
| } |
| |
| |
| // |
| // Generate 'unsafe' array copy stub |
| // Though just as safe as the other stubs, it takes an unscaled |
| // size_t argument instead of an element count. |
| // |
| // Input: |
| // c_rarg0 - source array address |
| // c_rarg1 - destination array address |
| // c_rarg2 - byte count, treated as ssize_t, can be zero |
| // |
| // Examines the alignment of the operands and dispatches |
| // to a long, int, short, or byte copy loop. |
| // |
| address generate_unsafe_copy(address byte_copy_entry, |
| address short_copy_entry, |
| address int_copy_entry, |
| address long_copy_entry) { |
| StubGenStubId stub_id = StubGenStubId::unsafe_arraycopy_id; |
| |
| Label L_long_aligned, L_int_aligned, L_short_aligned; |
| Register s = c_rarg0, d = c_rarg1, count = c_rarg2; |
| |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| // bump this on entry, not on exit: |
| inc_counter_np(SharedRuntime::_unsafe_array_copy_ctr); |
| |
| __ orr(rscratch1, s, d); |
| __ orr(rscratch1, rscratch1, count); |
| |
| __ andr(rscratch1, rscratch1, BytesPerLong-1); |
| __ cbz(rscratch1, L_long_aligned); |
| __ andr(rscratch1, rscratch1, BytesPerInt-1); |
| __ cbz(rscratch1, L_int_aligned); |
| __ tbz(rscratch1, 0, L_short_aligned); |
| __ b(RuntimeAddress(byte_copy_entry)); |
| |
| __ BIND(L_short_aligned); |
| __ lsr(count, count, LogBytesPerShort); // size => short_count |
| __ b(RuntimeAddress(short_copy_entry)); |
| __ BIND(L_int_aligned); |
| __ lsr(count, count, LogBytesPerInt); // size => int_count |
| __ b(RuntimeAddress(int_copy_entry)); |
| __ BIND(L_long_aligned); |
| __ lsr(count, count, LogBytesPerLong); // size => long_count |
| __ b(RuntimeAddress(long_copy_entry)); |
| |
| return start; |
| } |
| |
| // |
| // Generate generic array copy stubs |
| // |
| // Input: |
| // c_rarg0 - src oop |
| // c_rarg1 - src_pos (32-bits) |
| // c_rarg2 - dst oop |
| // c_rarg3 - dst_pos (32-bits) |
| // c_rarg4 - element count (32-bits) |
| // |
| // Output: |
| // r0 == 0 - success |
| // r0 == -1^K - failure, where K is partial transfer count |
| // |
| address generate_generic_copy(address byte_copy_entry, address short_copy_entry, |
| address int_copy_entry, address oop_copy_entry, |
| address long_copy_entry, address checkcast_copy_entry) { |
| StubGenStubId stub_id = StubGenStubId::generic_arraycopy_id; |
| |
| Label L_failed, L_objArray; |
| Label L_copy_bytes, L_copy_shorts, L_copy_ints, L_copy_longs; |
| |
| // Input registers |
| const Register src = c_rarg0; // source array oop |
| const Register src_pos = c_rarg1; // source position |
| const Register dst = c_rarg2; // destination array oop |
| const Register dst_pos = c_rarg3; // destination position |
| const Register length = c_rarg4; |
| |
| |
| // Registers used as temps |
| const Register dst_klass = c_rarg5; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| // bump this on entry, not on exit: |
| inc_counter_np(SharedRuntime::_generic_array_copy_ctr); |
| |
| //----------------------------------------------------------------------- |
| // Assembler stub will be used for this call to arraycopy |
| // if the following conditions are met: |
| // |
| // (1) src and dst must not be null. |
| // (2) src_pos must not be negative. |
| // (3) dst_pos must not be negative. |
| // (4) length must not be negative. |
| // (5) src klass and dst klass should be the same and not null. |
| // (6) src and dst should be arrays. |
| // (7) src_pos + length must not exceed length of src. |
| // (8) dst_pos + length must not exceed length of dst. |
| // |
| |
| // if (src == nullptr) return -1; |
| __ cbz(src, L_failed); |
| |
| // if (src_pos < 0) return -1; |
| __ tbnz(src_pos, 31, L_failed); // i.e. sign bit set |
| |
| // if (dst == nullptr) return -1; |
| __ cbz(dst, L_failed); |
| |
| // if (dst_pos < 0) return -1; |
| __ tbnz(dst_pos, 31, L_failed); // i.e. sign bit set |
| |
| // registers used as temp |
| const Register scratch_length = r16; // elements count to copy |
| const Register scratch_src_klass = r17; // array klass |
| const Register lh = r15; // layout helper |
| |
| // if (length < 0) return -1; |
| __ movw(scratch_length, length); // length (elements count, 32-bits value) |
| __ tbnz(scratch_length, 31, L_failed); // i.e. sign bit set |
| |
| __ load_klass(scratch_src_klass, src); |
| #ifdef ASSERT |
| // assert(src->klass() != nullptr); |
| { |
| BLOCK_COMMENT("assert klasses not null {"); |
| Label L1, L2; |
| __ cbnz(scratch_src_klass, L2); // it is broken if klass is null |
| __ bind(L1); |
| __ stop("broken null klass"); |
| __ bind(L2); |
| __ load_klass(rscratch1, dst); |
| __ cbz(rscratch1, L1); // this would be broken also |
| BLOCK_COMMENT("} assert klasses not null done"); |
| } |
| #endif |
| |
| // Load layout helper (32-bits) |
| // |
| // |array_tag| | header_size | element_type | |log2_element_size| |
| // 32 30 24 16 8 2 0 |
| // |
| // array_tag: typeArray = 0x3, objArray = 0x2, non-array = 0x0 |
| // |
| |
| const int lh_offset = in_bytes(Klass::layout_helper_offset()); |
| |
| // Handle objArrays completely differently... |
| const jint objArray_lh = Klass::array_layout_helper(T_OBJECT); |
| __ ldrw(lh, Address(scratch_src_klass, lh_offset)); |
| __ movw(rscratch1, objArray_lh); |
| __ eorw(rscratch2, lh, rscratch1); |
| __ cbzw(rscratch2, L_objArray); |
| |
| // if (src->klass() != dst->klass()) return -1; |
| __ load_klass(rscratch2, dst); |
| __ eor(rscratch2, rscratch2, scratch_src_klass); |
| __ cbnz(rscratch2, L_failed); |
| |
| // if (!src->is_Array()) return -1; |
| __ tbz(lh, 31, L_failed); // i.e. (lh >= 0) |
| |
| // At this point, it is known to be a typeArray (array_tag 0x3). |
| #ifdef ASSERT |
| { |
| BLOCK_COMMENT("assert primitive array {"); |
| Label L; |
| __ movw(rscratch2, Klass::_lh_array_tag_type_value << Klass::_lh_array_tag_shift); |
| __ cmpw(lh, rscratch2); |
| __ br(Assembler::GE, L); |
| __ stop("must be a primitive array"); |
| __ bind(L); |
| BLOCK_COMMENT("} assert primitive array done"); |
| } |
| #endif |
| |
| arraycopy_range_checks(src, src_pos, dst, dst_pos, scratch_length, |
| rscratch2, L_failed); |
| |
| // TypeArrayKlass |
| // |
| // src_addr = (src + array_header_in_bytes()) + (src_pos << log2elemsize); |
| // dst_addr = (dst + array_header_in_bytes()) + (dst_pos << log2elemsize); |
| // |
| |
| const Register rscratch1_offset = rscratch1; // array offset |
| const Register r15_elsize = lh; // element size |
| |
| __ ubfx(rscratch1_offset, lh, Klass::_lh_header_size_shift, |
| exact_log2(Klass::_lh_header_size_mask+1)); // array_offset |
| __ add(src, src, rscratch1_offset); // src array offset |
| __ add(dst, dst, rscratch1_offset); // dst array offset |
| BLOCK_COMMENT("choose copy loop based on element size"); |
| |
| // next registers should be set before the jump to corresponding stub |
| const Register from = c_rarg0; // source array address |
| const Register to = c_rarg1; // destination array address |
| const Register count = c_rarg2; // elements count |
| |
| // 'from', 'to', 'count' registers should be set in such order |
| // since they are the same as 'src', 'src_pos', 'dst'. |
| |
| assert(Klass::_lh_log2_element_size_shift == 0, "fix this code"); |
| |
| // The possible values of elsize are 0-3, i.e. exact_log2(element |
| // size in bytes). We do a simple bitwise binary search. |
| __ BIND(L_copy_bytes); |
| __ tbnz(r15_elsize, 1, L_copy_ints); |
| __ tbnz(r15_elsize, 0, L_copy_shorts); |
| __ lea(from, Address(src, src_pos));// src_addr |
| __ lea(to, Address(dst, dst_pos));// dst_addr |
| __ movw(count, scratch_length); // length |
| __ b(RuntimeAddress(byte_copy_entry)); |
| |
| __ BIND(L_copy_shorts); |
| __ lea(from, Address(src, src_pos, Address::lsl(1)));// src_addr |
| __ lea(to, Address(dst, dst_pos, Address::lsl(1)));// dst_addr |
| __ movw(count, scratch_length); // length |
| __ b(RuntimeAddress(short_copy_entry)); |
| |
| __ BIND(L_copy_ints); |
| __ tbnz(r15_elsize, 0, L_copy_longs); |
| __ lea(from, Address(src, src_pos, Address::lsl(2)));// src_addr |
| __ lea(to, Address(dst, dst_pos, Address::lsl(2)));// dst_addr |
| __ movw(count, scratch_length); // length |
| __ b(RuntimeAddress(int_copy_entry)); |
| |
| __ BIND(L_copy_longs); |
| #ifdef ASSERT |
| { |
| BLOCK_COMMENT("assert long copy {"); |
| Label L; |
| __ andw(lh, lh, Klass::_lh_log2_element_size_mask); // lh -> r15_elsize |
| __ cmpw(r15_elsize, LogBytesPerLong); |
| __ br(Assembler::EQ, L); |
| __ stop("must be long copy, but elsize is wrong"); |
| __ bind(L); |
| BLOCK_COMMENT("} assert long copy done"); |
| } |
| #endif |
| __ lea(from, Address(src, src_pos, Address::lsl(3)));// src_addr |
| __ lea(to, Address(dst, dst_pos, Address::lsl(3)));// dst_addr |
| __ movw(count, scratch_length); // length |
| __ b(RuntimeAddress(long_copy_entry)); |
| |
| // ObjArrayKlass |
| __ BIND(L_objArray); |
| // live at this point: scratch_src_klass, scratch_length, src[_pos], dst[_pos] |
| |
| Label L_plain_copy, L_checkcast_copy; |
| // test array classes for subtyping |
| __ load_klass(r15, dst); |
| __ cmp(scratch_src_klass, r15); // usual case is exact equality |
| __ br(Assembler::NE, L_checkcast_copy); |
| |
| // Identically typed arrays can be copied without element-wise checks. |
| arraycopy_range_checks(src, src_pos, dst, dst_pos, scratch_length, |
| rscratch2, L_failed); |
| |
| __ lea(from, Address(src, src_pos, Address::lsl(LogBytesPerHeapOop))); |
| __ add(from, from, arrayOopDesc::base_offset_in_bytes(T_OBJECT)); |
| __ lea(to, Address(dst, dst_pos, Address::lsl(LogBytesPerHeapOop))); |
| __ add(to, to, arrayOopDesc::base_offset_in_bytes(T_OBJECT)); |
| __ movw(count, scratch_length); // length |
| __ BIND(L_plain_copy); |
| __ b(RuntimeAddress(oop_copy_entry)); |
| |
| __ BIND(L_checkcast_copy); |
| // live at this point: scratch_src_klass, scratch_length, r15 (dst_klass) |
| { |
| // Before looking at dst.length, make sure dst is also an objArray. |
| __ ldrw(rscratch1, Address(r15, lh_offset)); |
| __ movw(rscratch2, objArray_lh); |
| __ eorw(rscratch1, rscratch1, rscratch2); |
| __ cbnzw(rscratch1, L_failed); |
| |
| // It is safe to examine both src.length and dst.length. |
| arraycopy_range_checks(src, src_pos, dst, dst_pos, scratch_length, |
| r15, L_failed); |
| |
| __ load_klass(dst_klass, dst); // reload |
| |
| // Marshal the base address arguments now, freeing registers. |
| __ lea(from, Address(src, src_pos, Address::lsl(LogBytesPerHeapOop))); |
| __ add(from, from, arrayOopDesc::base_offset_in_bytes(T_OBJECT)); |
| __ lea(to, Address(dst, dst_pos, Address::lsl(LogBytesPerHeapOop))); |
| __ add(to, to, arrayOopDesc::base_offset_in_bytes(T_OBJECT)); |
| __ movw(count, length); // length (reloaded) |
| Register sco_temp = c_rarg3; // this register is free now |
| assert_different_registers(from, to, count, sco_temp, |
| dst_klass, scratch_src_klass); |
| // assert_clean_int(count, sco_temp); |
| |
| // Generate the type check. |
| const int sco_offset = in_bytes(Klass::super_check_offset_offset()); |
| __ ldrw(sco_temp, Address(dst_klass, sco_offset)); |
| |
| // Smashes rscratch1, rscratch2 |
| generate_type_check(scratch_src_klass, sco_temp, dst_klass, /*temps*/ noreg, noreg, noreg, |
| L_plain_copy); |
| |
| // Fetch destination element klass from the ObjArrayKlass header. |
| int ek_offset = in_bytes(ObjArrayKlass::element_klass_offset()); |
| __ ldr(dst_klass, Address(dst_klass, ek_offset)); |
| __ ldrw(sco_temp, Address(dst_klass, sco_offset)); |
| |
| // the checkcast_copy loop needs two extra arguments: |
| assert(c_rarg3 == sco_temp, "#3 already in place"); |
| // Set up arguments for checkcast_copy_entry. |
| __ mov(c_rarg4, dst_klass); // dst.klass.element_klass |
| __ b(RuntimeAddress(checkcast_copy_entry)); |
| } |
| |
| __ BIND(L_failed); |
| __ mov(r0, -1); |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // |
| // Generate stub for array fill. If "aligned" is true, the |
| // "to" address is assumed to be heapword aligned. |
| // |
| // Arguments for generated stub: |
| // to: c_rarg0 |
| // value: c_rarg1 |
| // count: c_rarg2 treated as signed |
| // |
| address generate_fill(StubGenStubId stub_id) { |
| BasicType t; |
| bool aligned; |
| |
| switch (stub_id) { |
| case jbyte_fill_id: |
| t = T_BYTE; |
| aligned = false; |
| break; |
| case jshort_fill_id: |
| t = T_SHORT; |
| aligned = false; |
| break; |
| case jint_fill_id: |
| t = T_INT; |
| aligned = false; |
| break; |
| case arrayof_jbyte_fill_id: |
| t = T_BYTE; |
| aligned = true; |
| break; |
| case arrayof_jshort_fill_id: |
| t = T_SHORT; |
| aligned = true; |
| break; |
| case arrayof_jint_fill_id: |
| t = T_INT; |
| aligned = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| }; |
| |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| BLOCK_COMMENT("Entry:"); |
| |
| const Register to = c_rarg0; // source array address |
| const Register value = c_rarg1; // value |
| const Register count = c_rarg2; // elements count |
| |
| const Register bz_base = r10; // base for block_zero routine |
| const Register cnt_words = r11; // temp register |
| |
| __ enter(); |
| |
| Label L_fill_elements, L_exit1; |
| |
| int shift = -1; |
| switch (t) { |
| case T_BYTE: |
| shift = 0; |
| __ cmpw(count, 8 >> shift); // Short arrays (< 8 bytes) fill by element |
| __ bfi(value, value, 8, 8); // 8 bit -> 16 bit |
| __ bfi(value, value, 16, 16); // 16 bit -> 32 bit |
| __ br(Assembler::LO, L_fill_elements); |
| break; |
| case T_SHORT: |
| shift = 1; |
| __ cmpw(count, 8 >> shift); // Short arrays (< 8 bytes) fill by element |
| __ bfi(value, value, 16, 16); // 16 bit -> 32 bit |
| __ br(Assembler::LO, L_fill_elements); |
| break; |
| case T_INT: |
| shift = 2; |
| __ cmpw(count, 8 >> shift); // Short arrays (< 8 bytes) fill by element |
| __ br(Assembler::LO, L_fill_elements); |
| break; |
| default: ShouldNotReachHere(); |
| } |
| |
| // Align source address at 8 bytes address boundary. |
| Label L_skip_align1, L_skip_align2, L_skip_align4; |
| if (!aligned) { |
| switch (t) { |
| case T_BYTE: |
| // One byte misalignment happens only for byte arrays. |
| __ tbz(to, 0, L_skip_align1); |
| __ strb(value, Address(__ post(to, 1))); |
| __ subw(count, count, 1); |
| __ bind(L_skip_align1); |
| // Fallthrough |
| case T_SHORT: |
| // Two bytes misalignment happens only for byte and short (char) arrays. |
| __ tbz(to, 1, L_skip_align2); |
| __ strh(value, Address(__ post(to, 2))); |
| __ subw(count, count, 2 >> shift); |
| __ bind(L_skip_align2); |
| // Fallthrough |
| case T_INT: |
| // Align to 8 bytes, we know we are 4 byte aligned to start. |
| __ tbz(to, 2, L_skip_align4); |
| __ strw(value, Address(__ post(to, 4))); |
| __ subw(count, count, 4 >> shift); |
| __ bind(L_skip_align4); |
| break; |
| default: ShouldNotReachHere(); |
| } |
| } |
| |
| // |
| // Fill large chunks |
| // |
| __ lsrw(cnt_words, count, 3 - shift); // number of words |
| __ bfi(value, value, 32, 32); // 32 bit -> 64 bit |
| __ subw(count, count, cnt_words, Assembler::LSL, 3 - shift); |
| if (UseBlockZeroing) { |
| Label non_block_zeroing, rest; |
| // If the fill value is zero we can use the fast zero_words(). |
| __ cbnz(value, non_block_zeroing); |
| __ mov(bz_base, to); |
| __ add(to, to, cnt_words, Assembler::LSL, LogBytesPerWord); |
| address tpc = __ zero_words(bz_base, cnt_words); |
| if (tpc == nullptr) { |
| fatal("CodeCache is full at generate_fill"); |
| } |
| __ b(rest); |
| __ bind(non_block_zeroing); |
| __ fill_words(to, cnt_words, value); |
| __ bind(rest); |
| } else { |
| __ fill_words(to, cnt_words, value); |
| } |
| |
| // Remaining count is less than 8 bytes. Fill it by a single store. |
| // Note that the total length is no less than 8 bytes. |
| if (t == T_BYTE || t == T_SHORT) { |
| Label L_exit1; |
| __ cbzw(count, L_exit1); |
| __ add(to, to, count, Assembler::LSL, shift); // points to the end |
| __ str(value, Address(to, -8)); // overwrite some elements |
| __ bind(L_exit1); |
| __ leave(); |
| __ ret(lr); |
| } |
| |
| // Handle copies less than 8 bytes. |
| Label L_fill_2, L_fill_4, L_exit2; |
| __ bind(L_fill_elements); |
| switch (t) { |
| case T_BYTE: |
| __ tbz(count, 0, L_fill_2); |
| __ strb(value, Address(__ post(to, 1))); |
| __ bind(L_fill_2); |
| __ tbz(count, 1, L_fill_4); |
| __ strh(value, Address(__ post(to, 2))); |
| __ bind(L_fill_4); |
| __ tbz(count, 2, L_exit2); |
| __ strw(value, Address(to)); |
| break; |
| case T_SHORT: |
| __ tbz(count, 0, L_fill_4); |
| __ strh(value, Address(__ post(to, 2))); |
| __ bind(L_fill_4); |
| __ tbz(count, 1, L_exit2); |
| __ strw(value, Address(to)); |
| break; |
| case T_INT: |
| __ cbzw(count, L_exit2); |
| __ strw(value, Address(to)); |
| break; |
| default: ShouldNotReachHere(); |
| } |
| __ bind(L_exit2); |
| __ leave(); |
| __ ret(lr); |
| return start; |
| } |
| |
| address generate_unsafecopy_common_error_exit() { |
| address start_pc = __ pc(); |
| __ leave(); |
| __ mov(r0, 0); |
| __ ret(lr); |
| return start_pc; |
| } |
| |
| // |
| // Generate 'unsafe' set memory stub |
| // Though just as safe as the other stubs, it takes an unscaled |
| // size_t (# bytes) argument instead of an element count. |
| // |
| // This fill operation is atomicity preserving: as long as the |
| // address supplied is sufficiently aligned, all writes of up to 64 |
| // bits in size are single-copy atomic. |
| // |
| // Input: |
| // c_rarg0 - destination array address |
| // c_rarg1 - byte count (size_t) |
| // c_rarg2 - byte value |
| // |
| address generate_unsafe_setmemory() { |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, StubGenStubId::unsafe_setmemory_id); |
| address start = __ pc(); |
| |
| Register dest = c_rarg0, count = c_rarg1, value = c_rarg2; |
| Label tail; |
| |
| UnsafeMemoryAccessMark umam(this, true, false); |
| |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| __ dup(v0, __ T16B, value); |
| |
| if (AvoidUnalignedAccesses) { |
| __ cmp(count, (u1)16); |
| __ br(__ LO, tail); |
| |
| __ mov(rscratch1, 16); |
| __ andr(rscratch2, dest, 15); |
| __ sub(rscratch1, rscratch1, rscratch2); // Bytes needed to 16-align dest |
| __ strq(v0, Address(dest)); |
| __ sub(count, count, rscratch1); |
| __ add(dest, dest, rscratch1); |
| } |
| |
| __ subs(count, count, (u1)64); |
| __ br(__ LO, tail); |
| { |
| Label again; |
| __ bind(again); |
| __ stpq(v0, v0, Address(dest)); |
| __ stpq(v0, v0, Address(dest, 32)); |
| |
| __ subs(count, count, 64); |
| __ add(dest, dest, 64); |
| __ br(__ HS, again); |
| } |
| |
| __ bind(tail); |
| // The count of bytes is off by 64, but we don't need to correct |
| // it because we're only going to use the least-significant few |
| // count bits from here on. |
| // __ add(count, count, 64); |
| |
| { |
| Label dont; |
| __ tbz(count, exact_log2(32), dont); |
| __ stpq(v0, v0, __ post(dest, 32)); |
| __ bind(dont); |
| } |
| { |
| Label dont; |
| __ tbz(count, exact_log2(16), dont); |
| __ strq(v0, __ post(dest, 16)); |
| __ bind(dont); |
| } |
| { |
| Label dont; |
| __ tbz(count, exact_log2(8), dont); |
| __ strd(v0, __ post(dest, 8)); |
| __ bind(dont); |
| } |
| |
| Label finished; |
| __ tst(count, 7); |
| __ br(__ EQ, finished); |
| |
| { |
| Label dont; |
| __ tbz(count, exact_log2(4), dont); |
| __ strs(v0, __ post(dest, 4)); |
| __ bind(dont); |
| } |
| { |
| Label dont; |
| __ tbz(count, exact_log2(2), dont); |
| __ bfi(value, value, 8, 8); |
| __ strh(value, __ post(dest, 2)); |
| __ bind(dont); |
| } |
| { |
| Label dont; |
| __ tbz(count, exact_log2(1), dont); |
| __ strb(value, Address(dest)); |
| __ bind(dont); |
| } |
| |
| __ bind(finished); |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| address generate_data_cache_writeback() { |
| const Register line = c_rarg0; // address of line to write back |
| |
| __ align(CodeEntryAlignment); |
| |
| StubGenStubId stub_id = StubGenStubId::data_cache_writeback_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| __ enter(); |
| __ cache_wb(Address(line, 0)); |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| address generate_data_cache_writeback_sync() { |
| const Register is_pre = c_rarg0; // pre or post sync |
| |
| __ align(CodeEntryAlignment); |
| |
| StubGenStubId stub_id = StubGenStubId::data_cache_writeback_sync_id; |
| StubCodeMark mark(this, stub_id); |
| |
| // pre wbsync is a no-op |
| // post wbsync translates to an sfence |
| |
| Label skip; |
| address start = __ pc(); |
| __ enter(); |
| __ cbnz(is_pre, skip); |
| __ cache_wbsync(false); |
| __ bind(skip); |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| void generate_arraycopy_stubs() { |
| address entry; |
| address entry_jbyte_arraycopy; |
| address entry_jshort_arraycopy; |
| address entry_jint_arraycopy; |
| address entry_oop_arraycopy; |
| address entry_jlong_arraycopy; |
| address entry_checkcast_arraycopy; |
| |
| address ucm_common_error_exit = generate_unsafecopy_common_error_exit(); |
| UnsafeMemoryAccess::set_common_exit_stub_pc(ucm_common_error_exit); |
| |
| generate_copy_longs(StubGenStubId::copy_byte_f_id, IN_HEAP | IS_ARRAY, copy_f, r0, r1, r15); |
| generate_copy_longs(StubGenStubId::copy_byte_b_id, IN_HEAP | IS_ARRAY, copy_b, r0, r1, r15); |
| |
| generate_copy_longs(StubGenStubId::copy_oop_f_id, IN_HEAP | IS_ARRAY, copy_obj_f, r0, r1, r15); |
| generate_copy_longs(StubGenStubId::copy_oop_b_id, IN_HEAP | IS_ARRAY, copy_obj_b, r0, r1, r15); |
| |
| generate_copy_longs(StubGenStubId::copy_oop_uninit_f_id, IN_HEAP | IS_ARRAY | IS_DEST_UNINITIALIZED, copy_obj_uninit_f, r0, r1, r15); |
| generate_copy_longs(StubGenStubId::copy_oop_uninit_b_id, IN_HEAP | IS_ARRAY | IS_DEST_UNINITIALIZED, copy_obj_uninit_b, r0, r1, r15); |
| |
| StubRoutines::aarch64::_zero_blocks = generate_zero_blocks(); |
| |
| //*** jbyte |
| // Always need aligned and unaligned versions |
| StubRoutines::_jbyte_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::jbyte_disjoint_arraycopy_id, &entry); |
| StubRoutines::_jbyte_arraycopy = generate_conjoint_copy(StubGenStubId::jbyte_arraycopy_id, entry, &entry_jbyte_arraycopy); |
| StubRoutines::_arrayof_jbyte_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::arrayof_jbyte_disjoint_arraycopy_id, &entry); |
| StubRoutines::_arrayof_jbyte_arraycopy = generate_conjoint_copy(StubGenStubId::arrayof_jbyte_arraycopy_id, entry, nullptr); |
| |
| //*** jshort |
| // Always need aligned and unaligned versions |
| StubRoutines::_jshort_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::jshort_disjoint_arraycopy_id, &entry); |
| StubRoutines::_jshort_arraycopy = generate_conjoint_copy(StubGenStubId::jshort_arraycopy_id, entry, &entry_jshort_arraycopy); |
| StubRoutines::_arrayof_jshort_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::arrayof_jshort_disjoint_arraycopy_id, &entry); |
| StubRoutines::_arrayof_jshort_arraycopy = generate_conjoint_copy(StubGenStubId::arrayof_jshort_arraycopy_id, entry, nullptr); |
| |
| //*** jint |
| // Aligned versions |
| StubRoutines::_arrayof_jint_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::arrayof_jint_disjoint_arraycopy_id, &entry); |
| StubRoutines::_arrayof_jint_arraycopy = generate_conjoint_copy(StubGenStubId::arrayof_jint_arraycopy_id, entry, &entry_jint_arraycopy); |
| // In 64 bit we need both aligned and unaligned versions of jint arraycopy. |
| // entry_jint_arraycopy always points to the unaligned version |
| StubRoutines::_jint_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::jint_disjoint_arraycopy_id, &entry); |
| StubRoutines::_jint_arraycopy = generate_conjoint_copy(StubGenStubId::jint_arraycopy_id, entry, &entry_jint_arraycopy); |
| |
| //*** jlong |
| // It is always aligned |
| StubRoutines::_arrayof_jlong_disjoint_arraycopy = generate_disjoint_copy(StubGenStubId::arrayof_jlong_disjoint_arraycopy_id, &entry); |
| StubRoutines::_arrayof_jlong_arraycopy = generate_conjoint_copy(StubGenStubId::arrayof_jlong_arraycopy_id, entry, &entry_jlong_arraycopy); |
| StubRoutines::_jlong_disjoint_arraycopy = StubRoutines::_arrayof_jlong_disjoint_arraycopy; |
| StubRoutines::_jlong_arraycopy = StubRoutines::_arrayof_jlong_arraycopy; |
| |
| //*** oops |
| { |
| // With compressed oops we need unaligned versions; notice that |
| // we overwrite entry_oop_arraycopy. |
| bool aligned = !UseCompressedOops; |
| |
| StubRoutines::_arrayof_oop_disjoint_arraycopy |
| = generate_disjoint_copy(StubGenStubId::arrayof_oop_disjoint_arraycopy_id, &entry); |
| StubRoutines::_arrayof_oop_arraycopy |
| = generate_conjoint_copy(StubGenStubId::arrayof_oop_arraycopy_id, entry, &entry_oop_arraycopy); |
| // Aligned versions without pre-barriers |
| StubRoutines::_arrayof_oop_disjoint_arraycopy_uninit |
| = generate_disjoint_copy(StubGenStubId::arrayof_oop_disjoint_arraycopy_uninit_id, &entry); |
| StubRoutines::_arrayof_oop_arraycopy_uninit |
| = generate_conjoint_copy(StubGenStubId::arrayof_oop_arraycopy_uninit_id, entry, nullptr); |
| } |
| |
| StubRoutines::_oop_disjoint_arraycopy = StubRoutines::_arrayof_oop_disjoint_arraycopy; |
| StubRoutines::_oop_arraycopy = StubRoutines::_arrayof_oop_arraycopy; |
| StubRoutines::_oop_disjoint_arraycopy_uninit = StubRoutines::_arrayof_oop_disjoint_arraycopy_uninit; |
| StubRoutines::_oop_arraycopy_uninit = StubRoutines::_arrayof_oop_arraycopy_uninit; |
| |
| StubRoutines::_checkcast_arraycopy = generate_checkcast_copy(StubGenStubId::checkcast_arraycopy_id, &entry_checkcast_arraycopy); |
| StubRoutines::_checkcast_arraycopy_uninit = generate_checkcast_copy(StubGenStubId::checkcast_arraycopy_uninit_id, nullptr); |
| |
| StubRoutines::_unsafe_arraycopy = generate_unsafe_copy(entry_jbyte_arraycopy, |
| entry_jshort_arraycopy, |
| entry_jint_arraycopy, |
| entry_jlong_arraycopy); |
| |
| StubRoutines::_generic_arraycopy = generate_generic_copy(entry_jbyte_arraycopy, |
| entry_jshort_arraycopy, |
| entry_jint_arraycopy, |
| entry_oop_arraycopy, |
| entry_jlong_arraycopy, |
| entry_checkcast_arraycopy); |
| |
| StubRoutines::_jbyte_fill = generate_fill(StubGenStubId::jbyte_fill_id); |
| StubRoutines::_jshort_fill = generate_fill(StubGenStubId::jshort_fill_id); |
| StubRoutines::_jint_fill = generate_fill(StubGenStubId::jint_fill_id); |
| StubRoutines::_arrayof_jbyte_fill = generate_fill(StubGenStubId::arrayof_jbyte_fill_id); |
| StubRoutines::_arrayof_jshort_fill = generate_fill(StubGenStubId::arrayof_jshort_fill_id); |
| StubRoutines::_arrayof_jint_fill = generate_fill(StubGenStubId::arrayof_jint_fill_id); |
| } |
| |
| void generate_math_stubs() { Unimplemented(); } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - source byte array address |
| // c_rarg1 - destination byte array address |
| // c_rarg2 - K (key) in little endian int array |
| // |
| address generate_aescrypt_encryptBlock() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::aescrypt_encryptBlock_id; |
| StubCodeMark mark(this, stub_id); |
| |
| const Register from = c_rarg0; // source array address |
| const Register to = c_rarg1; // destination array address |
| const Register key = c_rarg2; // key array address |
| const Register keylen = rscratch1; |
| |
| address start = __ pc(); |
| __ enter(); |
| |
| __ ldrw(keylen, Address(key, arrayOopDesc::length_offset_in_bytes() - arrayOopDesc::base_offset_in_bytes(T_INT))); |
| |
| __ aesenc_loadkeys(key, keylen); |
| __ aesecb_encrypt(from, to, keylen); |
| |
| __ mov(r0, 0); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - source byte array address |
| // c_rarg1 - destination byte array address |
| // c_rarg2 - K (key) in little endian int array |
| // |
| address generate_aescrypt_decryptBlock() { |
| assert(UseAES, "need AES cryptographic extension support"); |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::aescrypt_decryptBlock_id; |
| StubCodeMark mark(this, stub_id); |
| Label L_doLast; |
| |
| const Register from = c_rarg0; // source array address |
| const Register to = c_rarg1; // destination array address |
| const Register key = c_rarg2; // key array address |
| const Register keylen = rscratch1; |
| |
| address start = __ pc(); |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| __ ldrw(keylen, Address(key, arrayOopDesc::length_offset_in_bytes() - arrayOopDesc::base_offset_in_bytes(T_INT))); |
| |
| __ aesecb_decrypt(from, to, key, keylen); |
| |
| __ mov(r0, 0); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - source byte array address |
| // c_rarg1 - destination byte array address |
| // c_rarg2 - K (key) in little endian int array |
| // c_rarg3 - r vector byte array address |
| // c_rarg4 - input length |
| // |
| // Output: |
| // x0 - input length |
| // |
| address generate_cipherBlockChaining_encryptAESCrypt() { |
| assert(UseAES, "need AES cryptographic extension support"); |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::cipherBlockChaining_encryptAESCrypt_id; |
| StubCodeMark mark(this, stub_id); |
| |
| Label L_loadkeys_44, L_loadkeys_52, L_aes_loop, L_rounds_44, L_rounds_52; |
| |
| const Register from = c_rarg0; // source array address |
| const Register to = c_rarg1; // destination array address |
| const Register key = c_rarg2; // key array address |
| const Register rvec = c_rarg3; // r byte array initialized from initvector array address |
| // and left with the results of the last encryption block |
| const Register len_reg = c_rarg4; // src len (must be multiple of blocksize 16) |
| const Register keylen = rscratch1; |
| |
| address start = __ pc(); |
| |
| __ enter(); |
| |
| __ movw(rscratch2, len_reg); |
| |
| __ ldrw(keylen, Address(key, arrayOopDesc::length_offset_in_bytes() - arrayOopDesc::base_offset_in_bytes(T_INT))); |
| |
| __ ld1(v0, __ T16B, rvec); |
| |
| __ cmpw(keylen, 52); |
| __ br(Assembler::CC, L_loadkeys_44); |
| __ br(Assembler::EQ, L_loadkeys_52); |
| |
| __ ld1(v17, v18, __ T16B, __ post(key, 32)); |
| __ rev32(v17, __ T16B, v17); |
| __ rev32(v18, __ T16B, v18); |
| __ BIND(L_loadkeys_52); |
| __ ld1(v19, v20, __ T16B, __ post(key, 32)); |
| __ rev32(v19, __ T16B, v19); |
| __ rev32(v20, __ T16B, v20); |
| __ BIND(L_loadkeys_44); |
| __ ld1(v21, v22, v23, v24, __ T16B, __ post(key, 64)); |
| __ rev32(v21, __ T16B, v21); |
| __ rev32(v22, __ T16B, v22); |
| __ rev32(v23, __ T16B, v23); |
| __ rev32(v24, __ T16B, v24); |
| __ ld1(v25, v26, v27, v28, __ T16B, __ post(key, 64)); |
| __ rev32(v25, __ T16B, v25); |
| __ rev32(v26, __ T16B, v26); |
| __ rev32(v27, __ T16B, v27); |
| __ rev32(v28, __ T16B, v28); |
| __ ld1(v29, v30, v31, __ T16B, key); |
| __ rev32(v29, __ T16B, v29); |
| __ rev32(v30, __ T16B, v30); |
| __ rev32(v31, __ T16B, v31); |
| |
| __ BIND(L_aes_loop); |
| __ ld1(v1, __ T16B, __ post(from, 16)); |
| __ eor(v0, __ T16B, v0, v1); |
| |
| __ br(Assembler::CC, L_rounds_44); |
| __ br(Assembler::EQ, L_rounds_52); |
| |
| __ aese(v0, v17); __ aesmc(v0, v0); |
| __ aese(v0, v18); __ aesmc(v0, v0); |
| __ BIND(L_rounds_52); |
| __ aese(v0, v19); __ aesmc(v0, v0); |
| __ aese(v0, v20); __ aesmc(v0, v0); |
| __ BIND(L_rounds_44); |
| __ aese(v0, v21); __ aesmc(v0, v0); |
| __ aese(v0, v22); __ aesmc(v0, v0); |
| __ aese(v0, v23); __ aesmc(v0, v0); |
| __ aese(v0, v24); __ aesmc(v0, v0); |
| __ aese(v0, v25); __ aesmc(v0, v0); |
| __ aese(v0, v26); __ aesmc(v0, v0); |
| __ aese(v0, v27); __ aesmc(v0, v0); |
| __ aese(v0, v28); __ aesmc(v0, v0); |
| __ aese(v0, v29); __ aesmc(v0, v0); |
| __ aese(v0, v30); |
| __ eor(v0, __ T16B, v0, v31); |
| |
| __ st1(v0, __ T16B, __ post(to, 16)); |
| |
| __ subw(len_reg, len_reg, 16); |
| __ cbnzw(len_reg, L_aes_loop); |
| |
| __ st1(v0, __ T16B, rvec); |
| |
| __ mov(r0, rscratch2); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - source byte array address |
| // c_rarg1 - destination byte array address |
| // c_rarg2 - K (key) in little endian int array |
| // c_rarg3 - r vector byte array address |
| // c_rarg4 - input length |
| // |
| // Output: |
| // r0 - input length |
| // |
| address generate_cipherBlockChaining_decryptAESCrypt() { |
| assert(UseAES, "need AES cryptographic extension support"); |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::cipherBlockChaining_decryptAESCrypt_id; |
| StubCodeMark mark(this, stub_id); |
| |
| Label L_loadkeys_44, L_loadkeys_52, L_aes_loop, L_rounds_44, L_rounds_52; |
| |
| const Register from = c_rarg0; // source array address |
| const Register to = c_rarg1; // destination array address |
| const Register key = c_rarg2; // key array address |
| const Register rvec = c_rarg3; // r byte array initialized from initvector array address |
| // and left with the results of the last encryption block |
| const Register len_reg = c_rarg4; // src len (must be multiple of blocksize 16) |
| const Register keylen = rscratch1; |
| |
| address start = __ pc(); |
| |
| __ enter(); |
| |
| __ movw(rscratch2, len_reg); |
| |
| __ ldrw(keylen, Address(key, arrayOopDesc::length_offset_in_bytes() - arrayOopDesc::base_offset_in_bytes(T_INT))); |
| |
| __ ld1(v2, __ T16B, rvec); |
| |
| __ ld1(v31, __ T16B, __ post(key, 16)); |
| __ rev32(v31, __ T16B, v31); |
| |
| __ cmpw(keylen, 52); |
| __ br(Assembler::CC, L_loadkeys_44); |
| __ br(Assembler::EQ, L_loadkeys_52); |
| |
| __ ld1(v17, v18, __ T16B, __ post(key, 32)); |
| __ rev32(v17, __ T16B, v17); |
| __ rev32(v18, __ T16B, v18); |
| __ BIND(L_loadkeys_52); |
| __ ld1(v19, v20, __ T16B, __ post(key, 32)); |
| __ rev32(v19, __ T16B, v19); |
| __ rev32(v20, __ T16B, v20); |
| __ BIND(L_loadkeys_44); |
| __ ld1(v21, v22, v23, v24, __ T16B, __ post(key, 64)); |
| __ rev32(v21, __ T16B, v21); |
| __ rev32(v22, __ T16B, v22); |
| __ rev32(v23, __ T16B, v23); |
| __ rev32(v24, __ T16B, v24); |
| __ ld1(v25, v26, v27, v28, __ T16B, __ post(key, 64)); |
| __ rev32(v25, __ T16B, v25); |
| __ rev32(v26, __ T16B, v26); |
| __ rev32(v27, __ T16B, v27); |
| __ rev32(v28, __ T16B, v28); |
| __ ld1(v29, v30, __ T16B, key); |
| __ rev32(v29, __ T16B, v29); |
| __ rev32(v30, __ T16B, v30); |
| |
| __ BIND(L_aes_loop); |
| __ ld1(v0, __ T16B, __ post(from, 16)); |
| __ orr(v1, __ T16B, v0, v0); |
| |
| __ br(Assembler::CC, L_rounds_44); |
| __ br(Assembler::EQ, L_rounds_52); |
| |
| __ aesd(v0, v17); __ aesimc(v0, v0); |
| __ aesd(v0, v18); __ aesimc(v0, v0); |
| __ BIND(L_rounds_52); |
| __ aesd(v0, v19); __ aesimc(v0, v0); |
| __ aesd(v0, v20); __ aesimc(v0, v0); |
| __ BIND(L_rounds_44); |
| __ aesd(v0, v21); __ aesimc(v0, v0); |
| __ aesd(v0, v22); __ aesimc(v0, v0); |
| __ aesd(v0, v23); __ aesimc(v0, v0); |
| __ aesd(v0, v24); __ aesimc(v0, v0); |
| __ aesd(v0, v25); __ aesimc(v0, v0); |
| __ aesd(v0, v26); __ aesimc(v0, v0); |
| __ aesd(v0, v27); __ aesimc(v0, v0); |
| __ aesd(v0, v28); __ aesimc(v0, v0); |
| __ aesd(v0, v29); __ aesimc(v0, v0); |
| __ aesd(v0, v30); |
| __ eor(v0, __ T16B, v0, v31); |
| __ eor(v0, __ T16B, v0, v2); |
| |
| __ st1(v0, __ T16B, __ post(to, 16)); |
| __ orr(v2, __ T16B, v1, v1); |
| |
| __ subw(len_reg, len_reg, 16); |
| __ cbnzw(len_reg, L_aes_loop); |
| |
| __ st1(v2, __ T16B, rvec); |
| |
| __ mov(r0, rscratch2); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Big-endian 128-bit + 64-bit -> 128-bit addition. |
| // Inputs: 128-bits. in is preserved. |
| // The least-significant 64-bit word is in the upper dword of each vector. |
| // inc (the 64-bit increment) is preserved. Its lower dword must be zero. |
| // Output: result |
| void be_add_128_64(FloatRegister result, FloatRegister in, |
| FloatRegister inc, FloatRegister tmp) { |
| assert_different_registers(result, tmp, inc); |
| |
| __ addv(result, __ T2D, in, inc); // Add inc to the least-significant dword of |
| // input |
| __ cm(__ HI, tmp, __ T2D, inc, result);// Check for result overflowing |
| __ ext(tmp, __ T16B, tmp, tmp, 0x08); // Swap LSD of comparison result to MSD and |
| // MSD == 0 (must be!) to LSD |
| __ subv(result, __ T2D, result, tmp); // Subtract -1 from MSD if there was an overflow |
| } |
| |
| // CTR AES crypt. |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - source byte array address |
| // c_rarg1 - destination byte array address |
| // c_rarg2 - K (key) in little endian int array |
| // c_rarg3 - counter vector byte array address |
| // c_rarg4 - input length |
| // c_rarg5 - saved encryptedCounter start |
| // c_rarg6 - saved used length |
| // |
| // Output: |
| // r0 - input length |
| // |
| address generate_counterMode_AESCrypt() { |
| const Register in = c_rarg0; |
| const Register out = c_rarg1; |
| const Register key = c_rarg2; |
| const Register counter = c_rarg3; |
| const Register saved_len = c_rarg4, len = r10; |
| const Register saved_encrypted_ctr = c_rarg5; |
| const Register used_ptr = c_rarg6, used = r12; |
| |
| const Register offset = r7; |
| const Register keylen = r11; |
| |
| const unsigned char block_size = 16; |
| const int bulk_width = 4; |
| // NB: bulk_width can be 4 or 8. 8 gives slightly faster |
| // performance with larger data sizes, but it also means that the |
| // fast path isn't used until you have at least 8 blocks, and up |
| // to 127 bytes of data will be executed on the slow path. For |
| // that reason, and also so as not to blow away too much icache, 4 |
| // blocks seems like a sensible compromise. |
| |
| // Algorithm: |
| // |
| // if (len == 0) { |
| // goto DONE; |
| // } |
| // int result = len; |
| // do { |
| // if (used >= blockSize) { |
| // if (len >= bulk_width * blockSize) { |
| // CTR_large_block(); |
| // if (len == 0) |
| // goto DONE; |
| // } |
| // for (;;) { |
| // 16ByteVector v0 = counter; |
| // embeddedCipher.encryptBlock(v0, 0, encryptedCounter, 0); |
| // used = 0; |
| // if (len < blockSize) |
| // break; /* goto NEXT */ |
| // 16ByteVector v1 = load16Bytes(in, offset); |
| // v1 = v1 ^ encryptedCounter; |
| // store16Bytes(out, offset); |
| // used = blockSize; |
| // offset += blockSize; |
| // len -= blockSize; |
| // if (len == 0) |
| // goto DONE; |
| // } |
| // } |
| // NEXT: |
| // out[outOff++] = (byte)(in[inOff++] ^ encryptedCounter[used++]); |
| // len--; |
| // } while (len != 0); |
| // DONE: |
| // return result; |
| // |
| // CTR_large_block() |
| // Wide bulk encryption of whole blocks. |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::counterMode_AESCrypt_id; |
| StubCodeMark mark(this, stub_id); |
| const address start = __ pc(); |
| __ enter(); |
| |
| Label DONE, CTR_large_block, large_block_return; |
| __ ldrw(used, Address(used_ptr)); |
| __ cbzw(saved_len, DONE); |
| |
| __ mov(len, saved_len); |
| __ mov(offset, 0); |
| |
| // Compute #rounds for AES based on the length of the key array |
| __ ldrw(keylen, Address(key, arrayOopDesc::length_offset_in_bytes() - arrayOopDesc::base_offset_in_bytes(T_INT))); |
| |
| __ aesenc_loadkeys(key, keylen); |
| |
| { |
| Label L_CTR_loop, NEXT; |
| |
| __ bind(L_CTR_loop); |
| |
| __ cmp(used, block_size); |
| __ br(__ LO, NEXT); |
| |
| // Maybe we have a lot of data |
| __ subsw(rscratch1, len, bulk_width * block_size); |
| __ br(__ HS, CTR_large_block); |
| __ BIND(large_block_return); |
| __ cbzw(len, DONE); |
| |
| // Setup the counter |
| __ movi(v4, __ T4S, 0); |
| __ movi(v5, __ T4S, 1); |
| __ ins(v4, __ S, v5, 2, 2); // v4 contains { 0, 1 } |
| |
| // 128-bit big-endian increment |
| __ ld1(v0, __ T16B, counter); |
| __ rev64(v16, __ T16B, v0); |
| be_add_128_64(v16, v16, v4, /*tmp*/v5); |
| __ rev64(v16, __ T16B, v16); |
| __ st1(v16, __ T16B, counter); |
| // Previous counter value is in v0 |
| // v4 contains { 0, 1 } |
| |
| { |
| // We have fewer than bulk_width blocks of data left. Encrypt |
| // them one by one until there is less than a full block |
| // remaining, being careful to save both the encrypted counter |
| // and the counter. |
| |
| Label inner_loop; |
| __ bind(inner_loop); |
| // Counter to encrypt is in v0 |
| __ aesecb_encrypt(noreg, noreg, keylen); |
| __ st1(v0, __ T16B, saved_encrypted_ctr); |
| |
| // Do we have a remaining full block? |
| |
| __ mov(used, 0); |
| __ cmp(len, block_size); |
| __ br(__ LO, NEXT); |
| |
| // Yes, we have a full block |
| __ ldrq(v1, Address(in, offset)); |
| __ eor(v1, __ T16B, v1, v0); |
| __ strq(v1, Address(out, offset)); |
| __ mov(used, block_size); |
| __ add(offset, offset, block_size); |
| |
| __ subw(len, len, block_size); |
| __ cbzw(len, DONE); |
| |
| // Increment the counter, store it back |
| __ orr(v0, __ T16B, v16, v16); |
| __ rev64(v16, __ T16B, v16); |
| be_add_128_64(v16, v16, v4, /*tmp*/v5); |
| __ rev64(v16, __ T16B, v16); |
| __ st1(v16, __ T16B, counter); // Save the incremented counter back |
| |
| __ b(inner_loop); |
| } |
| |
| __ BIND(NEXT); |
| |
| // Encrypt a single byte, and loop. |
| // We expect this to be a rare event. |
| __ ldrb(rscratch1, Address(in, offset)); |
| __ ldrb(rscratch2, Address(saved_encrypted_ctr, used)); |
| __ eor(rscratch1, rscratch1, rscratch2); |
| __ strb(rscratch1, Address(out, offset)); |
| __ add(offset, offset, 1); |
| __ add(used, used, 1); |
| __ subw(len, len,1); |
| __ cbnzw(len, L_CTR_loop); |
| } |
| |
| __ bind(DONE); |
| __ strw(used, Address(used_ptr)); |
| __ mov(r0, saved_len); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ ret(lr); |
| |
| // Bulk encryption |
| |
| __ BIND (CTR_large_block); |
| assert(bulk_width == 4 || bulk_width == 8, "must be"); |
| |
| if (bulk_width == 8) { |
| __ sub(sp, sp, 4 * 16); |
| __ st1(v12, v13, v14, v15, __ T16B, Address(sp)); |
| } |
| __ sub(sp, sp, 4 * 16); |
| __ st1(v8, v9, v10, v11, __ T16B, Address(sp)); |
| RegSet saved_regs = (RegSet::of(in, out, offset) |
| + RegSet::of(saved_encrypted_ctr, used_ptr, len)); |
| __ push(saved_regs, sp); |
| __ andr(len, len, -16 * bulk_width); // 8/4 encryptions, 16 bytes per encryption |
| __ add(in, in, offset); |
| __ add(out, out, offset); |
| |
| // Keys should already be loaded into the correct registers |
| |
| __ ld1(v0, __ T16B, counter); // v0 contains the first counter |
| __ rev64(v16, __ T16B, v0); // v16 contains byte-reversed counter |
| |
| // AES/CTR loop |
| { |
| Label L_CTR_loop; |
| __ BIND(L_CTR_loop); |
| |
| // Setup the counters |
| __ movi(v8, __ T4S, 0); |
| __ movi(v9, __ T4S, 1); |
| __ ins(v8, __ S, v9, 2, 2); // v8 contains { 0, 1 } |
| |
| for (int i = 0; i < bulk_width; i++) { |
| FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + i); |
| __ rev64(v0_ofs, __ T16B, v16); |
| be_add_128_64(v16, v16, v8, /*tmp*/v9); |
| } |
| |
| __ ld1(v8, v9, v10, v11, __ T16B, __ post(in, 4 * 16)); |
| |
| // Encrypt the counters |
| __ aesecb_encrypt(noreg, noreg, keylen, v0, bulk_width); |
| |
| if (bulk_width == 8) { |
| __ ld1(v12, v13, v14, v15, __ T16B, __ post(in, 4 * 16)); |
| } |
| |
| // XOR the encrypted counters with the inputs |
| for (int i = 0; i < bulk_width; i++) { |
| FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + i); |
| FloatRegister v8_ofs = as_FloatRegister(v8->encoding() + i); |
| __ eor(v0_ofs, __ T16B, v0_ofs, v8_ofs); |
| } |
| |
| // Write the encrypted data |
| __ st1(v0, v1, v2, v3, __ T16B, __ post(out, 4 * 16)); |
| if (bulk_width == 8) { |
| __ st1(v4, v5, v6, v7, __ T16B, __ post(out, 4 * 16)); |
| } |
| |
| __ subw(len, len, 16 * bulk_width); |
| __ cbnzw(len, L_CTR_loop); |
| } |
| |
| // Save the counter back where it goes |
| __ rev64(v16, __ T16B, v16); |
| __ st1(v16, __ T16B, counter); |
| |
| __ pop(saved_regs, sp); |
| |
| __ ld1(v8, v9, v10, v11, __ T16B, __ post(sp, 4 * 16)); |
| if (bulk_width == 8) { |
| __ ld1(v12, v13, v14, v15, __ T16B, __ post(sp, 4 * 16)); |
| } |
| |
| __ andr(rscratch1, len, -16 * bulk_width); |
| __ sub(len, len, rscratch1); |
| __ add(offset, offset, rscratch1); |
| __ mov(used, 16); |
| __ strw(used, Address(used_ptr)); |
| __ b(large_block_return); |
| |
| return start; |
| } |
| |
| // Vector AES Galois Counter Mode implementation. Parameters: |
| // |
| // in = c_rarg0 |
| // len = c_rarg1 |
| // ct = c_rarg2 - ciphertext that ghash will read (in for encrypt, out for decrypt) |
| // out = c_rarg3 |
| // key = c_rarg4 |
| // state = c_rarg5 - GHASH.state |
| // subkeyHtbl = c_rarg6 - powers of H |
| // counter = c_rarg7 - 16 bytes of CTR |
| // return - number of processed bytes |
| address generate_galoisCounterMode_AESCrypt() { |
| address ghash_polynomial = __ pc(); |
| __ emit_int64(0x87); // The low-order bits of the field |
| // polynomial (i.e. p = z^7+z^2+z+1) |
| // repeated in the low and high parts of a |
| // 128-bit vector |
| __ emit_int64(0x87); |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::galoisCounterMode_AESCrypt_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register in = c_rarg0; |
| const Register len = c_rarg1; |
| const Register ct = c_rarg2; |
| const Register out = c_rarg3; |
| // and updated with the incremented counter in the end |
| |
| const Register key = c_rarg4; |
| const Register state = c_rarg5; |
| |
| const Register subkeyHtbl = c_rarg6; |
| |
| const Register counter = c_rarg7; |
| |
| const Register keylen = r10; |
| // Save state before entering routine |
| __ sub(sp, sp, 4 * 16); |
| __ st1(v12, v13, v14, v15, __ T16B, Address(sp)); |
| __ sub(sp, sp, 4 * 16); |
| __ st1(v8, v9, v10, v11, __ T16B, Address(sp)); |
| |
| // __ andr(len, len, -512); |
| __ andr(len, len, -16 * 8); // 8 encryptions, 16 bytes per encryption |
| __ str(len, __ pre(sp, -2 * wordSize)); |
| |
| Label DONE; |
| __ cbz(len, DONE); |
| |
| // Compute #rounds for AES based on the length of the key array |
| __ ldrw(keylen, Address(key, arrayOopDesc::length_offset_in_bytes() - arrayOopDesc::base_offset_in_bytes(T_INT))); |
| |
| __ aesenc_loadkeys(key, keylen); |
| __ ld1(v0, __ T16B, counter); // v0 contains the first counter |
| __ rev32(v16, __ T16B, v0); // v16 contains byte-reversed counter |
| |
| // AES/CTR loop |
| { |
| Label L_CTR_loop; |
| __ BIND(L_CTR_loop); |
| |
| // Setup the counters |
| __ movi(v8, __ T4S, 0); |
| __ movi(v9, __ T4S, 1); |
| __ ins(v8, __ S, v9, 3, 3); // v8 contains { 0, 0, 0, 1 } |
| |
| assert(v0->encoding() < v8->encoding(), ""); |
| for (int i = v0->encoding(); i < v8->encoding(); i++) { |
| FloatRegister f = as_FloatRegister(i); |
| __ rev32(f, __ T16B, v16); |
| __ addv(v16, __ T4S, v16, v8); |
| } |
| |
| __ ld1(v8, v9, v10, v11, __ T16B, __ post(in, 4 * 16)); |
| |
| // Encrypt the counters |
| __ aesecb_encrypt(noreg, noreg, keylen, v0, /*unrolls*/8); |
| |
| __ ld1(v12, v13, v14, v15, __ T16B, __ post(in, 4 * 16)); |
| |
| // XOR the encrypted counters with the inputs |
| for (int i = 0; i < 8; i++) { |
| FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + i); |
| FloatRegister v8_ofs = as_FloatRegister(v8->encoding() + i); |
| __ eor(v0_ofs, __ T16B, v0_ofs, v8_ofs); |
| } |
| __ st1(v0, v1, v2, v3, __ T16B, __ post(out, 4 * 16)); |
| __ st1(v4, v5, v6, v7, __ T16B, __ post(out, 4 * 16)); |
| |
| __ subw(len, len, 16 * 8); |
| __ cbnzw(len, L_CTR_loop); |
| } |
| |
| __ rev32(v16, __ T16B, v16); |
| __ st1(v16, __ T16B, counter); |
| |
| __ ldr(len, Address(sp)); |
| __ lsr(len, len, exact_log2(16)); // We want the count of blocks |
| |
| // GHASH/CTR loop |
| __ ghash_processBlocks_wide(ghash_polynomial, state, subkeyHtbl, ct, |
| len, /*unrolls*/4); |
| |
| #ifdef ASSERT |
| { Label L; |
| __ cmp(len, (unsigned char)0); |
| __ br(Assembler::EQ, L); |
| __ stop("stubGenerator: abort"); |
| __ bind(L); |
| } |
| #endif |
| |
| __ bind(DONE); |
| // Return the number of bytes processed |
| __ ldr(r0, __ post(sp, 2 * wordSize)); |
| |
| __ ld1(v8, v9, v10, v11, __ T16B, __ post(sp, 4 * 16)); |
| __ ld1(v12, v13, v14, v15, __ T16B, __ post(sp, 4 * 16)); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ ret(lr); |
| return start; |
| } |
| |
| class Cached64Bytes { |
| private: |
| MacroAssembler *_masm; |
| Register _regs[8]; |
| |
| public: |
| Cached64Bytes(MacroAssembler *masm, RegSet rs): _masm(masm) { |
| assert(rs.size() == 8, "%u registers are used to cache 16 4-byte data", rs.size()); |
| auto it = rs.begin(); |
| for (auto &r: _regs) { |
| r = *it; |
| ++it; |
| } |
| } |
| |
| void gen_loads(Register base) { |
| for (int i = 0; i < 8; i += 2) { |
| __ ldp(_regs[i], _regs[i + 1], Address(base, 8 * i)); |
| } |
| } |
| |
| // Generate code extracting i-th unsigned word (4 bytes) from cached 64 bytes. |
| void extract_u32(Register dest, int i) { |
| __ ubfx(dest, _regs[i / 2], 32 * (i % 2), 32); |
| } |
| }; |
| |
| // Utility routines for md5. |
| // Clobbers r10 and r11. |
| void md5_FF(Cached64Bytes& reg_cache, Register r1, Register r2, Register r3, Register r4, |
| int k, int s, int t) { |
| Register rscratch3 = r10; |
| Register rscratch4 = r11; |
| |
| __ eorw(rscratch3, r3, r4); |
| __ movw(rscratch2, t); |
| __ andw(rscratch3, rscratch3, r2); |
| __ addw(rscratch4, r1, rscratch2); |
| reg_cache.extract_u32(rscratch1, k); |
| __ eorw(rscratch3, rscratch3, r4); |
| __ addw(rscratch4, rscratch4, rscratch1); |
| __ addw(rscratch3, rscratch3, rscratch4); |
| __ rorw(rscratch2, rscratch3, 32 - s); |
| __ addw(r1, rscratch2, r2); |
| } |
| |
| void md5_GG(Cached64Bytes& reg_cache, Register r1, Register r2, Register r3, Register r4, |
| int k, int s, int t) { |
| Register rscratch3 = r10; |
| Register rscratch4 = r11; |
| |
| reg_cache.extract_u32(rscratch1, k); |
| __ movw(rscratch2, t); |
| __ addw(rscratch4, r1, rscratch2); |
| __ addw(rscratch4, rscratch4, rscratch1); |
| __ bicw(rscratch2, r3, r4); |
| __ andw(rscratch3, r2, r4); |
| __ addw(rscratch2, rscratch2, rscratch4); |
| __ addw(rscratch2, rscratch2, rscratch3); |
| __ rorw(rscratch2, rscratch2, 32 - s); |
| __ addw(r1, rscratch2, r2); |
| } |
| |
| void md5_HH(Cached64Bytes& reg_cache, Register r1, Register r2, Register r3, Register r4, |
| int k, int s, int t) { |
| Register rscratch3 = r10; |
| Register rscratch4 = r11; |
| |
| __ eorw(rscratch3, r3, r4); |
| __ movw(rscratch2, t); |
| __ addw(rscratch4, r1, rscratch2); |
| reg_cache.extract_u32(rscratch1, k); |
| __ eorw(rscratch3, rscratch3, r2); |
| __ addw(rscratch4, rscratch4, rscratch1); |
| __ addw(rscratch3, rscratch3, rscratch4); |
| __ rorw(rscratch2, rscratch3, 32 - s); |
| __ addw(r1, rscratch2, r2); |
| } |
| |
| void md5_II(Cached64Bytes& reg_cache, Register r1, Register r2, Register r3, Register r4, |
| int k, int s, int t) { |
| Register rscratch3 = r10; |
| Register rscratch4 = r11; |
| |
| __ movw(rscratch3, t); |
| __ ornw(rscratch2, r2, r4); |
| __ addw(rscratch4, r1, rscratch3); |
| reg_cache.extract_u32(rscratch1, k); |
| __ eorw(rscratch3, rscratch2, r3); |
| __ addw(rscratch4, rscratch4, rscratch1); |
| __ addw(rscratch3, rscratch3, rscratch4); |
| __ rorw(rscratch2, rscratch3, 32 - s); |
| __ addw(r1, rscratch2, r2); |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - byte[] source+offset |
| // c_rarg1 - int[] SHA.state |
| // c_rarg2 - int offset |
| // c_rarg3 - int limit |
| // |
| address generate_md5_implCompress(StubGenStubId stub_id) { |
| bool multi_block; |
| switch (stub_id) { |
| case md5_implCompress_id: |
| multi_block = false; |
| break; |
| case md5_implCompressMB_id: |
| multi_block = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register buf = c_rarg0; |
| Register state = c_rarg1; |
| Register ofs = c_rarg2; |
| Register limit = c_rarg3; |
| Register a = r4; |
| Register b = r5; |
| Register c = r6; |
| Register d = r7; |
| Register rscratch3 = r10; |
| Register rscratch4 = r11; |
| |
| Register state_regs[2] = { r12, r13 }; |
| RegSet saved_regs = RegSet::range(r16, r22) - r18_tls; |
| Cached64Bytes reg_cache(_masm, RegSet::of(r14, r15) + saved_regs); // using 8 registers |
| |
| __ push(saved_regs, sp); |
| |
| __ ldp(state_regs[0], state_regs[1], Address(state)); |
| __ ubfx(a, state_regs[0], 0, 32); |
| __ ubfx(b, state_regs[0], 32, 32); |
| __ ubfx(c, state_regs[1], 0, 32); |
| __ ubfx(d, state_regs[1], 32, 32); |
| |
| Label md5_loop; |
| __ BIND(md5_loop); |
| |
| reg_cache.gen_loads(buf); |
| |
| // Round 1 |
| md5_FF(reg_cache, a, b, c, d, 0, 7, 0xd76aa478); |
| md5_FF(reg_cache, d, a, b, c, 1, 12, 0xe8c7b756); |
| md5_FF(reg_cache, c, d, a, b, 2, 17, 0x242070db); |
| md5_FF(reg_cache, b, c, d, a, 3, 22, 0xc1bdceee); |
| md5_FF(reg_cache, a, b, c, d, 4, 7, 0xf57c0faf); |
| md5_FF(reg_cache, d, a, b, c, 5, 12, 0x4787c62a); |
| md5_FF(reg_cache, c, d, a, b, 6, 17, 0xa8304613); |
| md5_FF(reg_cache, b, c, d, a, 7, 22, 0xfd469501); |
| md5_FF(reg_cache, a, b, c, d, 8, 7, 0x698098d8); |
| md5_FF(reg_cache, d, a, b, c, 9, 12, 0x8b44f7af); |
| md5_FF(reg_cache, c, d, a, b, 10, 17, 0xffff5bb1); |
| md5_FF(reg_cache, b, c, d, a, 11, 22, 0x895cd7be); |
| md5_FF(reg_cache, a, b, c, d, 12, 7, 0x6b901122); |
| md5_FF(reg_cache, d, a, b, c, 13, 12, 0xfd987193); |
| md5_FF(reg_cache, c, d, a, b, 14, 17, 0xa679438e); |
| md5_FF(reg_cache, b, c, d, a, 15, 22, 0x49b40821); |
| |
| // Round 2 |
| md5_GG(reg_cache, a, b, c, d, 1, 5, 0xf61e2562); |
| md5_GG(reg_cache, d, a, b, c, 6, 9, 0xc040b340); |
| md5_GG(reg_cache, c, d, a, b, 11, 14, 0x265e5a51); |
| md5_GG(reg_cache, b, c, d, a, 0, 20, 0xe9b6c7aa); |
| md5_GG(reg_cache, a, b, c, d, 5, 5, 0xd62f105d); |
| md5_GG(reg_cache, d, a, b, c, 10, 9, 0x02441453); |
| md5_GG(reg_cache, c, d, a, b, 15, 14, 0xd8a1e681); |
| md5_GG(reg_cache, b, c, d, a, 4, 20, 0xe7d3fbc8); |
| md5_GG(reg_cache, a, b, c, d, 9, 5, 0x21e1cde6); |
| md5_GG(reg_cache, d, a, b, c, 14, 9, 0xc33707d6); |
| md5_GG(reg_cache, c, d, a, b, 3, 14, 0xf4d50d87); |
| md5_GG(reg_cache, b, c, d, a, 8, 20, 0x455a14ed); |
| md5_GG(reg_cache, a, b, c, d, 13, 5, 0xa9e3e905); |
| md5_GG(reg_cache, d, a, b, c, 2, 9, 0xfcefa3f8); |
| md5_GG(reg_cache, c, d, a, b, 7, 14, 0x676f02d9); |
| md5_GG(reg_cache, b, c, d, a, 12, 20, 0x8d2a4c8a); |
| |
| // Round 3 |
| md5_HH(reg_cache, a, b, c, d, 5, 4, 0xfffa3942); |
| md5_HH(reg_cache, d, a, b, c, 8, 11, 0x8771f681); |
| md5_HH(reg_cache, c, d, a, b, 11, 16, 0x6d9d6122); |
| md5_HH(reg_cache, b, c, d, a, 14, 23, 0xfde5380c); |
| md5_HH(reg_cache, a, b, c, d, 1, 4, 0xa4beea44); |
| md5_HH(reg_cache, d, a, b, c, 4, 11, 0x4bdecfa9); |
| md5_HH(reg_cache, c, d, a, b, 7, 16, 0xf6bb4b60); |
| md5_HH(reg_cache, b, c, d, a, 10, 23, 0xbebfbc70); |
| md5_HH(reg_cache, a, b, c, d, 13, 4, 0x289b7ec6); |
| md5_HH(reg_cache, d, a, b, c, 0, 11, 0xeaa127fa); |
| md5_HH(reg_cache, c, d, a, b, 3, 16, 0xd4ef3085); |
| md5_HH(reg_cache, b, c, d, a, 6, 23, 0x04881d05); |
| md5_HH(reg_cache, a, b, c, d, 9, 4, 0xd9d4d039); |
| md5_HH(reg_cache, d, a, b, c, 12, 11, 0xe6db99e5); |
| md5_HH(reg_cache, c, d, a, b, 15, 16, 0x1fa27cf8); |
| md5_HH(reg_cache, b, c, d, a, 2, 23, 0xc4ac5665); |
| |
| // Round 4 |
| md5_II(reg_cache, a, b, c, d, 0, 6, 0xf4292244); |
| md5_II(reg_cache, d, a, b, c, 7, 10, 0x432aff97); |
| md5_II(reg_cache, c, d, a, b, 14, 15, 0xab9423a7); |
| md5_II(reg_cache, b, c, d, a, 5, 21, 0xfc93a039); |
| md5_II(reg_cache, a, b, c, d, 12, 6, 0x655b59c3); |
| md5_II(reg_cache, d, a, b, c, 3, 10, 0x8f0ccc92); |
| md5_II(reg_cache, c, d, a, b, 10, 15, 0xffeff47d); |
| md5_II(reg_cache, b, c, d, a, 1, 21, 0x85845dd1); |
| md5_II(reg_cache, a, b, c, d, 8, 6, 0x6fa87e4f); |
| md5_II(reg_cache, d, a, b, c, 15, 10, 0xfe2ce6e0); |
| md5_II(reg_cache, c, d, a, b, 6, 15, 0xa3014314); |
| md5_II(reg_cache, b, c, d, a, 13, 21, 0x4e0811a1); |
| md5_II(reg_cache, a, b, c, d, 4, 6, 0xf7537e82); |
| md5_II(reg_cache, d, a, b, c, 11, 10, 0xbd3af235); |
| md5_II(reg_cache, c, d, a, b, 2, 15, 0x2ad7d2bb); |
| md5_II(reg_cache, b, c, d, a, 9, 21, 0xeb86d391); |
| |
| __ addw(a, state_regs[0], a); |
| __ ubfx(rscratch2, state_regs[0], 32, 32); |
| __ addw(b, rscratch2, b); |
| __ addw(c, state_regs[1], c); |
| __ ubfx(rscratch4, state_regs[1], 32, 32); |
| __ addw(d, rscratch4, d); |
| |
| __ orr(state_regs[0], a, b, Assembler::LSL, 32); |
| __ orr(state_regs[1], c, d, Assembler::LSL, 32); |
| |
| if (multi_block) { |
| __ add(buf, buf, 64); |
| __ add(ofs, ofs, 64); |
| __ cmp(ofs, limit); |
| __ br(Assembler::LE, md5_loop); |
| __ mov(c_rarg0, ofs); // return ofs |
| } |
| |
| // write hash values back in the correct order |
| __ stp(state_regs[0], state_regs[1], Address(state)); |
| |
| __ pop(saved_regs, sp); |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - byte[] source+offset |
| // c_rarg1 - int[] SHA.state |
| // c_rarg2 - int offset |
| // c_rarg3 - int limit |
| // |
| address generate_sha1_implCompress(StubGenStubId stub_id) { |
| bool multi_block; |
| switch (stub_id) { |
| case sha1_implCompress_id: |
| multi_block = false; |
| break; |
| case sha1_implCompressMB_id: |
| multi_block = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register buf = c_rarg0; |
| Register state = c_rarg1; |
| Register ofs = c_rarg2; |
| Register limit = c_rarg3; |
| |
| Label keys; |
| Label sha1_loop; |
| |
| // load the keys into v0..v3 |
| __ adr(rscratch1, keys); |
| __ ld4r(v0, v1, v2, v3, __ T4S, Address(rscratch1)); |
| // load 5 words state into v6, v7 |
| __ ldrq(v6, Address(state, 0)); |
| __ ldrs(v7, Address(state, 16)); |
| |
| |
| __ BIND(sha1_loop); |
| // load 64 bytes of data into v16..v19 |
| __ ld1(v16, v17, v18, v19, __ T4S, multi_block ? __ post(buf, 64) : buf); |
| __ rev32(v16, __ T16B, v16); |
| __ rev32(v17, __ T16B, v17); |
| __ rev32(v18, __ T16B, v18); |
| __ rev32(v19, __ T16B, v19); |
| |
| // do the sha1 |
| __ addv(v4, __ T4S, v16, v0); |
| __ orr(v20, __ T16B, v6, v6); |
| |
| FloatRegister d0 = v16; |
| FloatRegister d1 = v17; |
| FloatRegister d2 = v18; |
| FloatRegister d3 = v19; |
| |
| for (int round = 0; round < 20; round++) { |
| FloatRegister tmp1 = (round & 1) ? v4 : v5; |
| FloatRegister tmp2 = (round & 1) ? v21 : v22; |
| FloatRegister tmp3 = round ? ((round & 1) ? v22 : v21) : v7; |
| FloatRegister tmp4 = (round & 1) ? v5 : v4; |
| FloatRegister key = (round < 4) ? v0 : ((round < 9) ? v1 : ((round < 14) ? v2 : v3)); |
| |
| if (round < 16) __ sha1su0(d0, __ T4S, d1, d2); |
| if (round < 19) __ addv(tmp1, __ T4S, d1, key); |
| __ sha1h(tmp2, __ T4S, v20); |
| if (round < 5) |
| __ sha1c(v20, __ T4S, tmp3, tmp4); |
| else if (round < 10 || round >= 15) |
| __ sha1p(v20, __ T4S, tmp3, tmp4); |
| else |
| __ sha1m(v20, __ T4S, tmp3, tmp4); |
| if (round < 16) __ sha1su1(d0, __ T4S, d3); |
| |
| tmp1 = d0; d0 = d1; d1 = d2; d2 = d3; d3 = tmp1; |
| } |
| |
| __ addv(v7, __ T2S, v7, v21); |
| __ addv(v6, __ T4S, v6, v20); |
| |
| if (multi_block) { |
| __ add(ofs, ofs, 64); |
| __ cmp(ofs, limit); |
| __ br(Assembler::LE, sha1_loop); |
| __ mov(c_rarg0, ofs); // return ofs |
| } |
| |
| __ strq(v6, Address(state, 0)); |
| __ strs(v7, Address(state, 16)); |
| |
| __ ret(lr); |
| |
| __ bind(keys); |
| __ emit_int32(0x5a827999); |
| __ emit_int32(0x6ed9eba1); |
| __ emit_int32(0x8f1bbcdc); |
| __ emit_int32(0xca62c1d6); |
| |
| return start; |
| } |
| |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - byte[] source+offset |
| // c_rarg1 - int[] SHA.state |
| // c_rarg2 - int offset |
| // c_rarg3 - int limit |
| // |
| address generate_sha256_implCompress(StubGenStubId stub_id) { |
| bool multi_block; |
| switch (stub_id) { |
| case sha256_implCompress_id: |
| multi_block = false; |
| break; |
| case sha256_implCompressMB_id: |
| multi_block = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| static const uint32_t round_consts[64] = { |
| 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, |
| 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, |
| 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, |
| 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, |
| 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, |
| 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, |
| 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, |
| 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, |
| 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, |
| 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, |
| 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, |
| 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, |
| 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, |
| 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, |
| 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, |
| 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, |
| }; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register buf = c_rarg0; |
| Register state = c_rarg1; |
| Register ofs = c_rarg2; |
| Register limit = c_rarg3; |
| |
| Label sha1_loop; |
| |
| __ stpd(v8, v9, __ pre(sp, -32)); |
| __ stpd(v10, v11, Address(sp, 16)); |
| |
| // dga == v0 |
| // dgb == v1 |
| // dg0 == v2 |
| // dg1 == v3 |
| // dg2 == v4 |
| // t0 == v6 |
| // t1 == v7 |
| |
| // load 16 keys to v16..v31 |
| __ lea(rscratch1, ExternalAddress((address)round_consts)); |
| __ ld1(v16, v17, v18, v19, __ T4S, __ post(rscratch1, 64)); |
| __ ld1(v20, v21, v22, v23, __ T4S, __ post(rscratch1, 64)); |
| __ ld1(v24, v25, v26, v27, __ T4S, __ post(rscratch1, 64)); |
| __ ld1(v28, v29, v30, v31, __ T4S, rscratch1); |
| |
| // load 8 words (256 bits) state |
| __ ldpq(v0, v1, state); |
| |
| __ BIND(sha1_loop); |
| // load 64 bytes of data into v8..v11 |
| __ ld1(v8, v9, v10, v11, __ T4S, multi_block ? __ post(buf, 64) : buf); |
| __ rev32(v8, __ T16B, v8); |
| __ rev32(v9, __ T16B, v9); |
| __ rev32(v10, __ T16B, v10); |
| __ rev32(v11, __ T16B, v11); |
| |
| __ addv(v6, __ T4S, v8, v16); |
| __ orr(v2, __ T16B, v0, v0); |
| __ orr(v3, __ T16B, v1, v1); |
| |
| FloatRegister d0 = v8; |
| FloatRegister d1 = v9; |
| FloatRegister d2 = v10; |
| FloatRegister d3 = v11; |
| |
| |
| for (int round = 0; round < 16; round++) { |
| FloatRegister tmp1 = (round & 1) ? v6 : v7; |
| FloatRegister tmp2 = (round & 1) ? v7 : v6; |
| FloatRegister tmp3 = (round & 1) ? v2 : v4; |
| FloatRegister tmp4 = (round & 1) ? v4 : v2; |
| |
| if (round < 12) __ sha256su0(d0, __ T4S, d1); |
| __ orr(v4, __ T16B, v2, v2); |
| if (round < 15) |
| __ addv(tmp1, __ T4S, d1, as_FloatRegister(round + 17)); |
| __ sha256h(v2, __ T4S, v3, tmp2); |
| __ sha256h2(v3, __ T4S, v4, tmp2); |
| if (round < 12) __ sha256su1(d0, __ T4S, d2, d3); |
| |
| tmp1 = d0; d0 = d1; d1 = d2; d2 = d3; d3 = tmp1; |
| } |
| |
| __ addv(v0, __ T4S, v0, v2); |
| __ addv(v1, __ T4S, v1, v3); |
| |
| if (multi_block) { |
| __ add(ofs, ofs, 64); |
| __ cmp(ofs, limit); |
| __ br(Assembler::LE, sha1_loop); |
| __ mov(c_rarg0, ofs); // return ofs |
| } |
| |
| __ ldpd(v10, v11, Address(sp, 16)); |
| __ ldpd(v8, v9, __ post(sp, 32)); |
| |
| __ stpq(v0, v1, state); |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Double rounds for sha512. |
| void sha512_dround(int dr, |
| FloatRegister vi0, FloatRegister vi1, |
| FloatRegister vi2, FloatRegister vi3, |
| FloatRegister vi4, FloatRegister vrc0, |
| FloatRegister vrc1, FloatRegister vin0, |
| FloatRegister vin1, FloatRegister vin2, |
| FloatRegister vin3, FloatRegister vin4) { |
| if (dr < 36) { |
| __ ld1(vrc1, __ T2D, __ post(rscratch2, 16)); |
| } |
| __ addv(v5, __ T2D, vrc0, vin0); |
| __ ext(v6, __ T16B, vi2, vi3, 8); |
| __ ext(v5, __ T16B, v5, v5, 8); |
| __ ext(v7, __ T16B, vi1, vi2, 8); |
| __ addv(vi3, __ T2D, vi3, v5); |
| if (dr < 32) { |
| __ ext(v5, __ T16B, vin3, vin4, 8); |
| __ sha512su0(vin0, __ T2D, vin1); |
| } |
| __ sha512h(vi3, __ T2D, v6, v7); |
| if (dr < 32) { |
| __ sha512su1(vin0, __ T2D, vin2, v5); |
| } |
| __ addv(vi4, __ T2D, vi1, vi3); |
| __ sha512h2(vi3, __ T2D, vi1, vi0); |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - byte[] source+offset |
| // c_rarg1 - int[] SHA.state |
| // c_rarg2 - int offset |
| // c_rarg3 - int limit |
| // |
| address generate_sha512_implCompress(StubGenStubId stub_id) { |
| bool multi_block; |
| switch (stub_id) { |
| case sha512_implCompress_id: |
| multi_block = false; |
| break; |
| case sha512_implCompressMB_id: |
| multi_block = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| static const uint64_t round_consts[80] = { |
| 0x428A2F98D728AE22L, 0x7137449123EF65CDL, 0xB5C0FBCFEC4D3B2FL, |
| 0xE9B5DBA58189DBBCL, 0x3956C25BF348B538L, 0x59F111F1B605D019L, |
| 0x923F82A4AF194F9BL, 0xAB1C5ED5DA6D8118L, 0xD807AA98A3030242L, |
| 0x12835B0145706FBEL, 0x243185BE4EE4B28CL, 0x550C7DC3D5FFB4E2L, |
| 0x72BE5D74F27B896FL, 0x80DEB1FE3B1696B1L, 0x9BDC06A725C71235L, |
| 0xC19BF174CF692694L, 0xE49B69C19EF14AD2L, 0xEFBE4786384F25E3L, |
| 0x0FC19DC68B8CD5B5L, 0x240CA1CC77AC9C65L, 0x2DE92C6F592B0275L, |
| 0x4A7484AA6EA6E483L, 0x5CB0A9DCBD41FBD4L, 0x76F988DA831153B5L, |
| 0x983E5152EE66DFABL, 0xA831C66D2DB43210L, 0xB00327C898FB213FL, |
| 0xBF597FC7BEEF0EE4L, 0xC6E00BF33DA88FC2L, 0xD5A79147930AA725L, |
| 0x06CA6351E003826FL, 0x142929670A0E6E70L, 0x27B70A8546D22FFCL, |
| 0x2E1B21385C26C926L, 0x4D2C6DFC5AC42AEDL, 0x53380D139D95B3DFL, |
| 0x650A73548BAF63DEL, 0x766A0ABB3C77B2A8L, 0x81C2C92E47EDAEE6L, |
| 0x92722C851482353BL, 0xA2BFE8A14CF10364L, 0xA81A664BBC423001L, |
| 0xC24B8B70D0F89791L, 0xC76C51A30654BE30L, 0xD192E819D6EF5218L, |
| 0xD69906245565A910L, 0xF40E35855771202AL, 0x106AA07032BBD1B8L, |
| 0x19A4C116B8D2D0C8L, 0x1E376C085141AB53L, 0x2748774CDF8EEB99L, |
| 0x34B0BCB5E19B48A8L, 0x391C0CB3C5C95A63L, 0x4ED8AA4AE3418ACBL, |
| 0x5B9CCA4F7763E373L, 0x682E6FF3D6B2B8A3L, 0x748F82EE5DEFB2FCL, |
| 0x78A5636F43172F60L, 0x84C87814A1F0AB72L, 0x8CC702081A6439ECL, |
| 0x90BEFFFA23631E28L, 0xA4506CEBDE82BDE9L, 0xBEF9A3F7B2C67915L, |
| 0xC67178F2E372532BL, 0xCA273ECEEA26619CL, 0xD186B8C721C0C207L, |
| 0xEADA7DD6CDE0EB1EL, 0xF57D4F7FEE6ED178L, 0x06F067AA72176FBAL, |
| 0x0A637DC5A2C898A6L, 0x113F9804BEF90DAEL, 0x1B710B35131C471BL, |
| 0x28DB77F523047D84L, 0x32CAAB7B40C72493L, 0x3C9EBE0A15C9BEBCL, |
| 0x431D67C49C100D4CL, 0x4CC5D4BECB3E42B6L, 0x597F299CFC657E2AL, |
| 0x5FCB6FAB3AD6FAECL, 0x6C44198C4A475817L |
| }; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register buf = c_rarg0; |
| Register state = c_rarg1; |
| Register ofs = c_rarg2; |
| Register limit = c_rarg3; |
| |
| __ stpd(v8, v9, __ pre(sp, -64)); |
| __ stpd(v10, v11, Address(sp, 16)); |
| __ stpd(v12, v13, Address(sp, 32)); |
| __ stpd(v14, v15, Address(sp, 48)); |
| |
| Label sha512_loop; |
| |
| // load state |
| __ ld1(v8, v9, v10, v11, __ T2D, state); |
| |
| // load first 4 round constants |
| __ lea(rscratch1, ExternalAddress((address)round_consts)); |
| __ ld1(v20, v21, v22, v23, __ T2D, __ post(rscratch1, 64)); |
| |
| __ BIND(sha512_loop); |
| // load 128B of data into v12..v19 |
| __ ld1(v12, v13, v14, v15, __ T2D, __ post(buf, 64)); |
| __ ld1(v16, v17, v18, v19, __ T2D, __ post(buf, 64)); |
| __ rev64(v12, __ T16B, v12); |
| __ rev64(v13, __ T16B, v13); |
| __ rev64(v14, __ T16B, v14); |
| __ rev64(v15, __ T16B, v15); |
| __ rev64(v16, __ T16B, v16); |
| __ rev64(v17, __ T16B, v17); |
| __ rev64(v18, __ T16B, v18); |
| __ rev64(v19, __ T16B, v19); |
| |
| __ mov(rscratch2, rscratch1); |
| |
| __ mov(v0, __ T16B, v8); |
| __ mov(v1, __ T16B, v9); |
| __ mov(v2, __ T16B, v10); |
| __ mov(v3, __ T16B, v11); |
| |
| sha512_dround( 0, v0, v1, v2, v3, v4, v20, v24, v12, v13, v19, v16, v17); |
| sha512_dround( 1, v3, v0, v4, v2, v1, v21, v25, v13, v14, v12, v17, v18); |
| sha512_dround( 2, v2, v3, v1, v4, v0, v22, v26, v14, v15, v13, v18, v19); |
| sha512_dround( 3, v4, v2, v0, v1, v3, v23, v27, v15, v16, v14, v19, v12); |
| sha512_dround( 4, v1, v4, v3, v0, v2, v24, v28, v16, v17, v15, v12, v13); |
| sha512_dround( 5, v0, v1, v2, v3, v4, v25, v29, v17, v18, v16, v13, v14); |
| sha512_dround( 6, v3, v0, v4, v2, v1, v26, v30, v18, v19, v17, v14, v15); |
| sha512_dround( 7, v2, v3, v1, v4, v0, v27, v31, v19, v12, v18, v15, v16); |
| sha512_dround( 8, v4, v2, v0, v1, v3, v28, v24, v12, v13, v19, v16, v17); |
| sha512_dround( 9, v1, v4, v3, v0, v2, v29, v25, v13, v14, v12, v17, v18); |
| sha512_dround(10, v0, v1, v2, v3, v4, v30, v26, v14, v15, v13, v18, v19); |
| sha512_dround(11, v3, v0, v4, v2, v1, v31, v27, v15, v16, v14, v19, v12); |
| sha512_dround(12, v2, v3, v1, v4, v0, v24, v28, v16, v17, v15, v12, v13); |
| sha512_dround(13, v4, v2, v0, v1, v3, v25, v29, v17, v18, v16, v13, v14); |
| sha512_dround(14, v1, v4, v3, v0, v2, v26, v30, v18, v19, v17, v14, v15); |
| sha512_dround(15, v0, v1, v2, v3, v4, v27, v31, v19, v12, v18, v15, v16); |
| sha512_dround(16, v3, v0, v4, v2, v1, v28, v24, v12, v13, v19, v16, v17); |
| sha512_dround(17, v2, v3, v1, v4, v0, v29, v25, v13, v14, v12, v17, v18); |
| sha512_dround(18, v4, v2, v0, v1, v3, v30, v26, v14, v15, v13, v18, v19); |
| sha512_dround(19, v1, v4, v3, v0, v2, v31, v27, v15, v16, v14, v19, v12); |
| sha512_dround(20, v0, v1, v2, v3, v4, v24, v28, v16, v17, v15, v12, v13); |
| sha512_dround(21, v3, v0, v4, v2, v1, v25, v29, v17, v18, v16, v13, v14); |
| sha512_dround(22, v2, v3, v1, v4, v0, v26, v30, v18, v19, v17, v14, v15); |
| sha512_dround(23, v4, v2, v0, v1, v3, v27, v31, v19, v12, v18, v15, v16); |
| sha512_dround(24, v1, v4, v3, v0, v2, v28, v24, v12, v13, v19, v16, v17); |
| sha512_dround(25, v0, v1, v2, v3, v4, v29, v25, v13, v14, v12, v17, v18); |
| sha512_dround(26, v3, v0, v4, v2, v1, v30, v26, v14, v15, v13, v18, v19); |
| sha512_dround(27, v2, v3, v1, v4, v0, v31, v27, v15, v16, v14, v19, v12); |
| sha512_dround(28, v4, v2, v0, v1, v3, v24, v28, v16, v17, v15, v12, v13); |
| sha512_dround(29, v1, v4, v3, v0, v2, v25, v29, v17, v18, v16, v13, v14); |
| sha512_dround(30, v0, v1, v2, v3, v4, v26, v30, v18, v19, v17, v14, v15); |
| sha512_dround(31, v3, v0, v4, v2, v1, v27, v31, v19, v12, v18, v15, v16); |
| sha512_dround(32, v2, v3, v1, v4, v0, v28, v24, v12, v0, v0, v0, v0); |
| sha512_dround(33, v4, v2, v0, v1, v3, v29, v25, v13, v0, v0, v0, v0); |
| sha512_dround(34, v1, v4, v3, v0, v2, v30, v26, v14, v0, v0, v0, v0); |
| sha512_dround(35, v0, v1, v2, v3, v4, v31, v27, v15, v0, v0, v0, v0); |
| sha512_dround(36, v3, v0, v4, v2, v1, v24, v0, v16, v0, v0, v0, v0); |
| sha512_dround(37, v2, v3, v1, v4, v0, v25, v0, v17, v0, v0, v0, v0); |
| sha512_dround(38, v4, v2, v0, v1, v3, v26, v0, v18, v0, v0, v0, v0); |
| sha512_dround(39, v1, v4, v3, v0, v2, v27, v0, v19, v0, v0, v0, v0); |
| |
| __ addv(v8, __ T2D, v8, v0); |
| __ addv(v9, __ T2D, v9, v1); |
| __ addv(v10, __ T2D, v10, v2); |
| __ addv(v11, __ T2D, v11, v3); |
| |
| if (multi_block) { |
| __ add(ofs, ofs, 128); |
| __ cmp(ofs, limit); |
| __ br(Assembler::LE, sha512_loop); |
| __ mov(c_rarg0, ofs); // return ofs |
| } |
| |
| __ st1(v8, v9, v10, v11, __ T2D, state); |
| |
| __ ldpd(v14, v15, Address(sp, 48)); |
| __ ldpd(v12, v13, Address(sp, 32)); |
| __ ldpd(v10, v11, Address(sp, 16)); |
| __ ldpd(v8, v9, __ post(sp, 64)); |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Execute one round of keccak of two computations in parallel. |
| // One of the states should be loaded into the lower halves of |
| // the vector registers v0-v24, the other should be loaded into |
| // the upper halves of those registers. The ld1r instruction loads |
| // the round constant into both halves of register v31. |
| // Intermediate results c0...c5 and d0...d5 are computed |
| // in registers v25...v30. |
| // All vector instructions that are used operate on both register |
| // halves in parallel. |
| // If only a single computation is needed, one can only load the lower halves. |
| void keccak_round(Register rscratch1) { |
| __ eor3(v29, __ T16B, v4, v9, v14); // c4 = a4 ^ a9 ^ a14 |
| __ eor3(v26, __ T16B, v1, v6, v11); // c1 = a1 ^ a16 ^ a11 |
| __ eor3(v28, __ T16B, v3, v8, v13); // c3 = a3 ^ a8 ^a13 |
| __ eor3(v25, __ T16B, v0, v5, v10); // c0 = a0 ^ a5 ^ a10 |
| __ eor3(v27, __ T16B, v2, v7, v12); // c2 = a2 ^ a7 ^ a12 |
| __ eor3(v29, __ T16B, v29, v19, v24); // c4 ^= a19 ^ a24 |
| __ eor3(v26, __ T16B, v26, v16, v21); // c1 ^= a16 ^ a21 |
| __ eor3(v28, __ T16B, v28, v18, v23); // c3 ^= a18 ^ a23 |
| __ eor3(v25, __ T16B, v25, v15, v20); // c0 ^= a15 ^ a20 |
| __ eor3(v27, __ T16B, v27, v17, v22); // c2 ^= a17 ^ a22 |
| |
| __ rax1(v30, __ T2D, v29, v26); // d0 = c4 ^ rol(c1, 1) |
| __ rax1(v26, __ T2D, v26, v28); // d2 = c1 ^ rol(c3, 1) |
| __ rax1(v28, __ T2D, v28, v25); // d4 = c3 ^ rol(c0, 1) |
| __ rax1(v25, __ T2D, v25, v27); // d1 = c0 ^ rol(c2, 1) |
| __ rax1(v27, __ T2D, v27, v29); // d3 = c2 ^ rol(c4, 1) |
| |
| __ eor(v0, __ T16B, v0, v30); // a0 = a0 ^ d0 |
| __ xar(v29, __ T2D, v1, v25, (64 - 1)); // a10' = rol((a1^d1), 1) |
| __ xar(v1, __ T2D, v6, v25, (64 - 44)); // a1 = rol(a6^d1), 44) |
| __ xar(v6, __ T2D, v9, v28, (64 - 20)); // a6 = rol((a9^d4), 20) |
| __ xar(v9, __ T2D, v22, v26, (64 - 61)); // a9 = rol((a22^d2), 61) |
| __ xar(v22, __ T2D, v14, v28, (64 - 39)); // a22 = rol((a14^d4), 39) |
| __ xar(v14, __ T2D, v20, v30, (64 - 18)); // a14 = rol((a20^d0), 18) |
| __ xar(v31, __ T2D, v2, v26, (64 - 62)); // a20' = rol((a2^d2), 62) |
| __ xar(v2, __ T2D, v12, v26, (64 - 43)); // a2 = rol((a12^d2), 43) |
| __ xar(v12, __ T2D, v13, v27, (64 - 25)); // a12 = rol((a13^d3), 25) |
| __ xar(v13, __ T2D, v19, v28, (64 - 8)); // a13 = rol((a19^d4), 8) |
| __ xar(v19, __ T2D, v23, v27, (64 - 56)); // a19 = rol((a23^d3), 56) |
| __ xar(v23, __ T2D, v15, v30, (64 - 41)); // a23 = rol((a15^d0), 41) |
| __ xar(v15, __ T2D, v4, v28, (64 - 27)); // a15 = rol((a4^d4), 27) |
| __ xar(v28, __ T2D, v24, v28, (64 - 14)); // a4' = rol((a24^d4), 14) |
| __ xar(v24, __ T2D, v21, v25, (64 - 2)); // a24 = rol((a21^d1), 2) |
| __ xar(v8, __ T2D, v8, v27, (64 - 55)); // a21' = rol((a8^d3), 55) |
| __ xar(v4, __ T2D, v16, v25, (64 - 45)); // a8' = rol((a16^d1), 45) |
| __ xar(v16, __ T2D, v5, v30, (64 - 36)); // a16 = rol((a5^d0), 36) |
| __ xar(v5, __ T2D, v3, v27, (64 - 28)); // a5 = rol((a3^d3), 28) |
| __ xar(v27, __ T2D, v18, v27, (64 - 21)); // a3' = rol((a18^d3), 21) |
| __ xar(v3, __ T2D, v17, v26, (64 - 15)); // a18' = rol((a17^d2), 15) |
| __ xar(v25, __ T2D, v11, v25, (64 - 10)); // a17' = rol((a11^d1), 10) |
| __ xar(v26, __ T2D, v7, v26, (64 - 6)); // a11' = rol((a7^d2), 6) |
| __ xar(v30, __ T2D, v10, v30, (64 - 3)); // a7' = rol((a10^d0), 3) |
| |
| __ bcax(v20, __ T16B, v31, v22, v8); // a20 = a20' ^ (~a21 & a22') |
| __ bcax(v21, __ T16B, v8, v23, v22); // a21 = a21' ^ (~a22 & a23) |
| __ bcax(v22, __ T16B, v22, v24, v23); // a22 = a22 ^ (~a23 & a24) |
| __ bcax(v23, __ T16B, v23, v31, v24); // a23 = a23 ^ (~a24 & a20') |
| __ bcax(v24, __ T16B, v24, v8, v31); // a24 = a24 ^ (~a20' & a21') |
| |
| __ ld1r(v31, __ T2D, __ post(rscratch1, 8)); // rc = round_constants[i] |
| |
| __ bcax(v17, __ T16B, v25, v19, v3); // a17 = a17' ^ (~a18' & a19) |
| __ bcax(v18, __ T16B, v3, v15, v19); // a18 = a18' ^ (~a19 & a15') |
| __ bcax(v19, __ T16B, v19, v16, v15); // a19 = a19 ^ (~a15 & a16) |
| __ bcax(v15, __ T16B, v15, v25, v16); // a15 = a15 ^ (~a16 & a17') |
| __ bcax(v16, __ T16B, v16, v3, v25); // a16 = a16 ^ (~a17' & a18') |
| |
| __ bcax(v10, __ T16B, v29, v12, v26); // a10 = a10' ^ (~a11' & a12) |
| __ bcax(v11, __ T16B, v26, v13, v12); // a11 = a11' ^ (~a12 & a13) |
| __ bcax(v12, __ T16B, v12, v14, v13); // a12 = a12 ^ (~a13 & a14) |
| __ bcax(v13, __ T16B, v13, v29, v14); // a13 = a13 ^ (~a14 & a10') |
| __ bcax(v14, __ T16B, v14, v26, v29); // a14 = a14 ^ (~a10' & a11') |
| |
| __ bcax(v7, __ T16B, v30, v9, v4); // a7 = a7' ^ (~a8' & a9) |
| __ bcax(v8, __ T16B, v4, v5, v9); // a8 = a8' ^ (~a9 & a5) |
| __ bcax(v9, __ T16B, v9, v6, v5); // a9 = a9 ^ (~a5 & a6) |
| __ bcax(v5, __ T16B, v5, v30, v6); // a5 = a5 ^ (~a6 & a7) |
| __ bcax(v6, __ T16B, v6, v4, v30); // a6 = a6 ^ (~a7 & a8') |
| |
| __ bcax(v3, __ T16B, v27, v0, v28); // a3 = a3' ^ (~a4' & a0) |
| __ bcax(v4, __ T16B, v28, v1, v0); // a4 = a4' ^ (~a0 & a1) |
| __ bcax(v0, __ T16B, v0, v2, v1); // a0 = a0 ^ (~a1 & a2) |
| __ bcax(v1, __ T16B, v1, v27, v2); // a1 = a1 ^ (~a2 & a3) |
| __ bcax(v2, __ T16B, v2, v28, v27); // a2 = a2 ^ (~a3 & a4') |
| |
| __ eor(v0, __ T16B, v0, v31); // a0 = a0 ^ rc |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - byte[] source+offset |
| // c_rarg1 - byte[] SHA.state |
| // c_rarg2 - int block_size |
| // c_rarg3 - int offset |
| // c_rarg4 - int limit |
| // |
| address generate_sha3_implCompress(StubGenStubId stub_id) { |
| bool multi_block; |
| switch (stub_id) { |
| case sha3_implCompress_id: |
| multi_block = false; |
| break; |
| case sha3_implCompressMB_id: |
| multi_block = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| static const uint64_t round_consts[24] = { |
| 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, |
| 0x8000000080008000L, 0x000000000000808BL, 0x0000000080000001L, |
| 0x8000000080008081L, 0x8000000000008009L, 0x000000000000008AL, |
| 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL, |
| 0x000000008000808BL, 0x800000000000008BL, 0x8000000000008089L, |
| 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, |
| 0x000000000000800AL, 0x800000008000000AL, 0x8000000080008081L, |
| 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L |
| }; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register buf = c_rarg0; |
| Register state = c_rarg1; |
| Register block_size = c_rarg2; |
| Register ofs = c_rarg3; |
| Register limit = c_rarg4; |
| |
| Label sha3_loop, rounds24_loop; |
| Label sha3_512_or_sha3_384, shake128; |
| |
| __ stpd(v8, v9, __ pre(sp, -64)); |
| __ stpd(v10, v11, Address(sp, 16)); |
| __ stpd(v12, v13, Address(sp, 32)); |
| __ stpd(v14, v15, Address(sp, 48)); |
| |
| // load state |
| __ add(rscratch1, state, 32); |
| __ ld1(v0, v1, v2, v3, __ T1D, state); |
| __ ld1(v4, v5, v6, v7, __ T1D, __ post(rscratch1, 32)); |
| __ ld1(v8, v9, v10, v11, __ T1D, __ post(rscratch1, 32)); |
| __ ld1(v12, v13, v14, v15, __ T1D, __ post(rscratch1, 32)); |
| __ ld1(v16, v17, v18, v19, __ T1D, __ post(rscratch1, 32)); |
| __ ld1(v20, v21, v22, v23, __ T1D, __ post(rscratch1, 32)); |
| __ ld1(v24, __ T1D, rscratch1); |
| |
| __ BIND(sha3_loop); |
| |
| // 24 keccak rounds |
| __ movw(rscratch2, 24); |
| |
| // load round_constants base |
| __ lea(rscratch1, ExternalAddress((address) round_consts)); |
| |
| // load input |
| __ ld1(v25, v26, v27, v28, __ T8B, __ post(buf, 32)); |
| __ ld1(v29, v30, v31, __ T8B, __ post(buf, 24)); |
| __ eor(v0, __ T8B, v0, v25); |
| __ eor(v1, __ T8B, v1, v26); |
| __ eor(v2, __ T8B, v2, v27); |
| __ eor(v3, __ T8B, v3, v28); |
| __ eor(v4, __ T8B, v4, v29); |
| __ eor(v5, __ T8B, v5, v30); |
| __ eor(v6, __ T8B, v6, v31); |
| |
| // block_size == 72, SHA3-512; block_size == 104, SHA3-384 |
| __ tbz(block_size, 7, sha3_512_or_sha3_384); |
| |
| __ ld1(v25, v26, v27, v28, __ T8B, __ post(buf, 32)); |
| __ ld1(v29, v30, v31, __ T8B, __ post(buf, 24)); |
| __ eor(v7, __ T8B, v7, v25); |
| __ eor(v8, __ T8B, v8, v26); |
| __ eor(v9, __ T8B, v9, v27); |
| __ eor(v10, __ T8B, v10, v28); |
| __ eor(v11, __ T8B, v11, v29); |
| __ eor(v12, __ T8B, v12, v30); |
| __ eor(v13, __ T8B, v13, v31); |
| |
| __ ld1(v25, v26, v27, __ T8B, __ post(buf, 24)); |
| __ eor(v14, __ T8B, v14, v25); |
| __ eor(v15, __ T8B, v15, v26); |
| __ eor(v16, __ T8B, v16, v27); |
| |
| // block_size == 136, bit4 == 0 and bit5 == 0, SHA3-256 or SHAKE256 |
| __ andw(c_rarg5, block_size, 48); |
| __ cbzw(c_rarg5, rounds24_loop); |
| |
| __ tbnz(block_size, 5, shake128); |
| // block_size == 144, bit5 == 0, SHA3-224 |
| __ ldrd(v28, __ post(buf, 8)); |
| __ eor(v17, __ T8B, v17, v28); |
| __ b(rounds24_loop); |
| |
| __ BIND(shake128); |
| __ ld1(v28, v29, v30, v31, __ T8B, __ post(buf, 32)); |
| __ eor(v17, __ T8B, v17, v28); |
| __ eor(v18, __ T8B, v18, v29); |
| __ eor(v19, __ T8B, v19, v30); |
| __ eor(v20, __ T8B, v20, v31); |
| __ b(rounds24_loop); // block_size == 168, SHAKE128 |
| |
| __ BIND(sha3_512_or_sha3_384); |
| __ ld1(v25, v26, __ T8B, __ post(buf, 16)); |
| __ eor(v7, __ T8B, v7, v25); |
| __ eor(v8, __ T8B, v8, v26); |
| __ tbz(block_size, 5, rounds24_loop); // SHA3-512 |
| |
| // SHA3-384 |
| __ ld1(v27, v28, v29, v30, __ T8B, __ post(buf, 32)); |
| __ eor(v9, __ T8B, v9, v27); |
| __ eor(v10, __ T8B, v10, v28); |
| __ eor(v11, __ T8B, v11, v29); |
| __ eor(v12, __ T8B, v12, v30); |
| |
| __ BIND(rounds24_loop); |
| __ subw(rscratch2, rscratch2, 1); |
| |
| keccak_round(rscratch1); |
| |
| __ cbnzw(rscratch2, rounds24_loop); |
| |
| if (multi_block) { |
| __ add(ofs, ofs, block_size); |
| __ cmp(ofs, limit); |
| __ br(Assembler::LE, sha3_loop); |
| __ mov(c_rarg0, ofs); // return ofs |
| } |
| |
| __ st1(v0, v1, v2, v3, __ T1D, __ post(state, 32)); |
| __ st1(v4, v5, v6, v7, __ T1D, __ post(state, 32)); |
| __ st1(v8, v9, v10, v11, __ T1D, __ post(state, 32)); |
| __ st1(v12, v13, v14, v15, __ T1D, __ post(state, 32)); |
| __ st1(v16, v17, v18, v19, __ T1D, __ post(state, 32)); |
| __ st1(v20, v21, v22, v23, __ T1D, __ post(state, 32)); |
| __ st1(v24, __ T1D, state); |
| |
| // restore callee-saved registers |
| __ ldpd(v14, v15, Address(sp, 48)); |
| __ ldpd(v12, v13, Address(sp, 32)); |
| __ ldpd(v10, v11, Address(sp, 16)); |
| __ ldpd(v8, v9, __ post(sp, 64)); |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Inputs: |
| // c_rarg0 - long[] state0 |
| // c_rarg1 - long[] state1 |
| address generate_double_keccak() { |
| static const uint64_t round_consts[24] = { |
| 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, |
| 0x8000000080008000L, 0x000000000000808BL, 0x0000000080000001L, |
| 0x8000000080008081L, 0x8000000000008009L, 0x000000000000008AL, |
| 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL, |
| 0x000000008000808BL, 0x800000000000008BL, 0x8000000000008089L, |
| 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, |
| 0x000000000000800AL, 0x800000008000000AL, 0x8000000080008081L, |
| 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L |
| }; |
| |
| // Implements the double_keccak() method of the |
| // sun.secyrity.provider.SHA3Parallel class |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, "StubRoutines", "double_keccak"); |
| address start = __ pc(); |
| __ enter(); |
| |
| Register state0 = c_rarg0; |
| Register state1 = c_rarg1; |
| |
| Label rounds24_loop; |
| |
| // save callee-saved registers |
| __ stpd(v8, v9, __ pre(sp, -64)); |
| __ stpd(v10, v11, Address(sp, 16)); |
| __ stpd(v12, v13, Address(sp, 32)); |
| __ stpd(v14, v15, Address(sp, 48)); |
| |
| // load states |
| __ add(rscratch1, state0, 32); |
| __ ld4(v0, v1, v2, v3, __ D, 0, state0); |
| __ ld4(v4, v5, v6, v7, __ D, 0, __ post(rscratch1, 32)); |
| __ ld4(v8, v9, v10, v11, __ D, 0, __ post(rscratch1, 32)); |
| __ ld4(v12, v13, v14, v15, __ D, 0, __ post(rscratch1, 32)); |
| __ ld4(v16, v17, v18, v19, __ D, 0, __ post(rscratch1, 32)); |
| __ ld4(v20, v21, v22, v23, __ D, 0, __ post(rscratch1, 32)); |
| __ ld1(v24, __ D, 0, rscratch1); |
| __ add(rscratch1, state1, 32); |
| __ ld4(v0, v1, v2, v3, __ D, 1, state1); |
| __ ld4(v4, v5, v6, v7, __ D, 1, __ post(rscratch1, 32)); |
| __ ld4(v8, v9, v10, v11, __ D, 1, __ post(rscratch1, 32)); |
| __ ld4(v12, v13, v14, v15, __ D, 1, __ post(rscratch1, 32)); |
| __ ld4(v16, v17, v18, v19, __ D, 1, __ post(rscratch1, 32)); |
| __ ld4(v20, v21, v22, v23, __ D, 1, __ post(rscratch1, 32)); |
| __ ld1(v24, __ D, 1, rscratch1); |
| |
| // 24 keccak rounds |
| __ movw(rscratch2, 24); |
| |
| // load round_constants base |
| __ lea(rscratch1, ExternalAddress((address) round_consts)); |
| |
| __ BIND(rounds24_loop); |
| __ subw(rscratch2, rscratch2, 1); |
| keccak_round(rscratch1); |
| __ cbnzw(rscratch2, rounds24_loop); |
| |
| __ st4(v0, v1, v2, v3, __ D, 0, __ post(state0, 32)); |
| __ st4(v4, v5, v6, v7, __ D, 0, __ post(state0, 32)); |
| __ st4(v8, v9, v10, v11, __ D, 0, __ post(state0, 32)); |
| __ st4(v12, v13, v14, v15, __ D, 0, __ post(state0, 32)); |
| __ st4(v16, v17, v18, v19, __ D, 0, __ post(state0, 32)); |
| __ st4(v20, v21, v22, v23, __ D, 0, __ post(state0, 32)); |
| __ st1(v24, __ D, 0, state0); |
| __ st4(v0, v1, v2, v3, __ D, 1, __ post(state1, 32)); |
| __ st4(v4, v5, v6, v7, __ D, 1, __ post(state1, 32)); |
| __ st4(v8, v9, v10, v11, __ D, 1, __ post(state1, 32)); |
| __ st4(v12, v13, v14, v15, __ D, 1, __ post(state1, 32)); |
| __ st4(v16, v17, v18, v19, __ D, 1, __ post(state1, 32)); |
| __ st4(v20, v21, v22, v23, __ D, 1, __ post(state1, 32)); |
| __ st1(v24, __ D, 1, state1); |
| |
| // restore callee-saved vector registers |
| __ ldpd(v14, v15, Address(sp, 48)); |
| __ ldpd(v12, v13, Address(sp, 32)); |
| __ ldpd(v10, v11, Address(sp, 16)); |
| __ ldpd(v8, v9, __ post(sp, 64)); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // ChaCha20 block function. This version parallelizes the 32-bit |
| // state elements on each of 16 vectors, producing 4 blocks of |
| // keystream at a time. |
| // |
| // state (int[16]) = c_rarg0 |
| // keystream (byte[256]) = c_rarg1 |
| // return - number of bytes of produced keystream (always 256) |
| // |
| // This implementation takes each 32-bit integer from the state |
| // array and broadcasts it across all 4 32-bit lanes of a vector register |
| // (e.g. state[0] is replicated on all 4 lanes of v4, state[1] to all 4 lanes |
| // of v5, etc.). Once all 16 elements have been broadcast onto 16 vectors, |
| // the quarter round schedule is implemented as outlined in RFC 7539 section |
| // 2.3. However, instead of sequentially processing the 3 quarter round |
| // operations represented by one QUARTERROUND function, we instead stack all |
| // the adds, xors and left-rotations from the first 4 quarter rounds together |
| // and then do the same for the second set of 4 quarter rounds. This removes |
| // some latency that would otherwise be incurred by waiting for an add to |
| // complete before performing an xor (which depends on the result of the |
| // add), etc. An adjustment happens between the first and second groups of 4 |
| // quarter rounds, but this is done only in the inputs to the macro functions |
| // that generate the assembly instructions - these adjustments themselves are |
| // not part of the resulting assembly. |
| // The 4 registers v0-v3 are used during the quarter round operations as |
| // scratch registers. Once the 20 rounds are complete, these 4 scratch |
| // registers become the vectors involved in adding the start state back onto |
| // the post-QR working state. After the adds are complete, each of the 16 |
| // vectors write their first lane back to the keystream buffer, followed |
| // by the second lane from all vectors and so on. |
| address generate_chacha20Block_blockpar() { |
| Label L_twoRounds, L_cc20_const; |
| // The constant data is broken into two 128-bit segments to be loaded |
| // onto FloatRegisters. The first 128 bits are a counter add overlay |
| // that adds +0/+1/+2/+3 to the vector holding replicated state[12]. |
| // The second 128-bits is a table constant used for 8-bit left rotations. |
| __ BIND(L_cc20_const); |
| __ emit_int64(0x0000000100000000UL); |
| __ emit_int64(0x0000000300000002UL); |
| __ emit_int64(0x0605040702010003UL); |
| __ emit_int64(0x0E0D0C0F0A09080BUL); |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::chacha20Block_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| int i, j; |
| const Register state = c_rarg0; |
| const Register keystream = c_rarg1; |
| const Register loopCtr = r10; |
| const Register tmpAddr = r11; |
| const FloatRegister ctrAddOverlay = v28; |
| const FloatRegister lrot8Tbl = v29; |
| |
| // Organize SIMD registers in an array that facilitates |
| // putting repetitive opcodes into loop structures. It is |
| // important that each grouping of 4 registers is monotonically |
| // increasing to support the requirements of multi-register |
| // instructions (e.g. ld4r, st4, etc.) |
| const FloatRegister workSt[16] = { |
| v4, v5, v6, v7, v16, v17, v18, v19, |
| v20, v21, v22, v23, v24, v25, v26, v27 |
| }; |
| |
| // Pull in constant data. The first 16 bytes are the add overlay |
| // which is applied to the vector holding the counter (state[12]). |
| // The second 16 bytes is the index register for the 8-bit left |
| // rotation tbl instruction. |
| __ adr(tmpAddr, L_cc20_const); |
| __ ldpq(ctrAddOverlay, lrot8Tbl, Address(tmpAddr)); |
| |
| // Load from memory and interlace across 16 SIMD registers, |
| // With each word from memory being broadcast to all lanes of |
| // each successive SIMD register. |
| // Addr(0) -> All lanes in workSt[i] |
| // Addr(4) -> All lanes workSt[i + 1], etc. |
| __ mov(tmpAddr, state); |
| for (i = 0; i < 16; i += 4) { |
| __ ld4r(workSt[i], workSt[i + 1], workSt[i + 2], workSt[i + 3], __ T4S, |
| __ post(tmpAddr, 16)); |
| } |
| __ addv(workSt[12], __ T4S, workSt[12], ctrAddOverlay); // Add ctr overlay |
| |
| // Before entering the loop, create 5 4-register arrays. These |
| // will hold the 4 registers that represent the a/b/c/d fields |
| // in the quarter round operation. For instance the "b" field |
| // for the first 4 quarter round operations is the set of v16/v17/v18/v19, |
| // but in the second 4 quarter rounds it gets adjusted to v17/v18/v19/v16 |
| // since it is part of a diagonal organization. The aSet and scratch |
| // register sets are defined at declaration time because they do not change |
| // organization at any point during the 20-round processing. |
| FloatRegister aSet[4] = { v4, v5, v6, v7 }; |
| FloatRegister bSet[4]; |
| FloatRegister cSet[4]; |
| FloatRegister dSet[4]; |
| FloatRegister scratch[4] = { v0, v1, v2, v3 }; |
| |
| // Set up the 10 iteration loop and perform all 8 quarter round ops |
| __ mov(loopCtr, 10); |
| __ BIND(L_twoRounds); |
| |
| // Set to columnar organization and do the following 4 quarter-rounds: |
| // QUARTERROUND(0, 4, 8, 12) |
| // QUARTERROUND(1, 5, 9, 13) |
| // QUARTERROUND(2, 6, 10, 14) |
| // QUARTERROUND(3, 7, 11, 15) |
| __ cc20_set_qr_registers(bSet, workSt, 4, 5, 6, 7); |
| __ cc20_set_qr_registers(cSet, workSt, 8, 9, 10, 11); |
| __ cc20_set_qr_registers(dSet, workSt, 12, 13, 14, 15); |
| |
| __ cc20_qr_add4(aSet, bSet); // a += b |
| __ cc20_qr_xor4(dSet, aSet, dSet); // d ^= a |
| __ cc20_qr_lrot4(dSet, dSet, 16, lrot8Tbl); // d <<<= 16 |
| |
| __ cc20_qr_add4(cSet, dSet); // c += d |
| __ cc20_qr_xor4(bSet, cSet, scratch); // b ^= c (scratch) |
| __ cc20_qr_lrot4(scratch, bSet, 12, lrot8Tbl); // b <<<= 12 |
| |
| __ cc20_qr_add4(aSet, bSet); // a += b |
| __ cc20_qr_xor4(dSet, aSet, dSet); // d ^= a |
| __ cc20_qr_lrot4(dSet, dSet, 8, lrot8Tbl); // d <<<= 8 |
| |
| __ cc20_qr_add4(cSet, dSet); // c += d |
| __ cc20_qr_xor4(bSet, cSet, scratch); // b ^= c (scratch) |
| __ cc20_qr_lrot4(scratch, bSet, 7, lrot8Tbl); // b <<<= 12 |
| |
| // Set to diagonal organization and do the next 4 quarter-rounds: |
| // QUARTERROUND(0, 5, 10, 15) |
| // QUARTERROUND(1, 6, 11, 12) |
| // QUARTERROUND(2, 7, 8, 13) |
| // QUARTERROUND(3, 4, 9, 14) |
| __ cc20_set_qr_registers(bSet, workSt, 5, 6, 7, 4); |
| __ cc20_set_qr_registers(cSet, workSt, 10, 11, 8, 9); |
| __ cc20_set_qr_registers(dSet, workSt, 15, 12, 13, 14); |
| |
| __ cc20_qr_add4(aSet, bSet); // a += b |
| __ cc20_qr_xor4(dSet, aSet, dSet); // d ^= a |
| __ cc20_qr_lrot4(dSet, dSet, 16, lrot8Tbl); // d <<<= 16 |
| |
| __ cc20_qr_add4(cSet, dSet); // c += d |
| __ cc20_qr_xor4(bSet, cSet, scratch); // b ^= c (scratch) |
| __ cc20_qr_lrot4(scratch, bSet, 12, lrot8Tbl); // b <<<= 12 |
| |
| __ cc20_qr_add4(aSet, bSet); // a += b |
| __ cc20_qr_xor4(dSet, aSet, dSet); // d ^= a |
| __ cc20_qr_lrot4(dSet, dSet, 8, lrot8Tbl); // d <<<= 8 |
| |
| __ cc20_qr_add4(cSet, dSet); // c += d |
| __ cc20_qr_xor4(bSet, cSet, scratch); // b ^= c (scratch) |
| __ cc20_qr_lrot4(scratch, bSet, 7, lrot8Tbl); // b <<<= 12 |
| |
| // Decrement and iterate |
| __ sub(loopCtr, loopCtr, 1); |
| __ cbnz(loopCtr, L_twoRounds); |
| |
| __ mov(tmpAddr, state); |
| |
| // Add the starting state back to the post-loop keystream |
| // state. We read/interlace the state array from memory into |
| // 4 registers similar to what we did in the beginning. Then |
| // add the counter overlay onto workSt[12] at the end. |
| for (i = 0; i < 16; i += 4) { |
| __ ld4r(v0, v1, v2, v3, __ T4S, __ post(tmpAddr, 16)); |
| __ addv(workSt[i], __ T4S, workSt[i], v0); |
| __ addv(workSt[i + 1], __ T4S, workSt[i + 1], v1); |
| __ addv(workSt[i + 2], __ T4S, workSt[i + 2], v2); |
| __ addv(workSt[i + 3], __ T4S, workSt[i + 3], v3); |
| } |
| __ addv(workSt[12], __ T4S, workSt[12], ctrAddOverlay); // Add ctr overlay |
| |
| // Write working state into the keystream buffer. This is accomplished |
| // by taking the lane "i" from each of the four vectors and writing |
| // it to consecutive 4-byte offsets, then post-incrementing by 16 and |
| // repeating with the next 4 vectors until all 16 vectors have been used. |
| // Then move to the next lane and repeat the process until all lanes have |
| // been written. |
| for (i = 0; i < 4; i++) { |
| for (j = 0; j < 16; j += 4) { |
| __ st4(workSt[j], workSt[j + 1], workSt[j + 2], workSt[j + 3], __ S, i, |
| __ post(keystream, 16)); |
| } |
| } |
| |
| __ mov(r0, 256); // Return length of output keystream |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Helpers to schedule parallel operation bundles across vector |
| // register sequences of size 2, 4 or 8. |
| |
| // Implement various primitive computations across vector sequences |
| |
| template<int N> |
| void vs_addv(const VSeq<N>& v, Assembler::SIMD_Arrangement T, |
| const VSeq<N>& v1, const VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ addv(v[i], T, v1[i], v2[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_subv(const VSeq<N>& v, Assembler::SIMD_Arrangement T, |
| const VSeq<N>& v1, const VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ subv(v[i], T, v1[i], v2[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_mulv(const VSeq<N>& v, Assembler::SIMD_Arrangement T, |
| const VSeq<N>& v1, const VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ mulv(v[i], T, v1[i], v2[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_negr(const VSeq<N>& v, Assembler::SIMD_Arrangement T, const VSeq<N>& v1) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ negr(v[i], T, v1[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_sshr(const VSeq<N>& v, Assembler::SIMD_Arrangement T, |
| const VSeq<N>& v1, int shift) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ sshr(v[i], T, v1[i], shift); |
| } |
| } |
| |
| template<int N> |
| void vs_andr(const VSeq<N>& v, const VSeq<N>& v1, const VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ andr(v[i], __ T16B, v1[i], v2[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_orr(const VSeq<N>& v, const VSeq<N>& v1, const VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ orr(v[i], __ T16B, v1[i], v2[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_notr(const VSeq<N>& v, const VSeq<N>& v1) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ notr(v[i], __ T16B, v1[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_sqdmulh(const VSeq<N>& v, Assembler::SIMD_Arrangement T, const VSeq<N>& v1, const VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ sqdmulh(v[i], T, v1[i], v2[i]); |
| } |
| } |
| |
| template<int N> |
| void vs_mlsv(const VSeq<N>& v, Assembler::SIMD_Arrangement T, const VSeq<N>& v1, VSeq<N>& v2) { |
| // output must not be constant |
| assert(N == 1 || !v.is_constant(), "cannot output multiple values to a constant vector"); |
| // output cannot overwrite pending inputs |
| assert(!vs_write_before_read(v, v1), "output overwrites input"); |
| assert(!vs_write_before_read(v, v2), "output overwrites input"); |
| for (int i = 0; i < N; i++) { |
| __ mlsv(v[i], T, v1[i], v2[i]); |
| } |
| } |
| |
| // load N/2 successive pairs of quadword values from memory in order |
| // into N successive vector registers of the sequence via the |
| // address supplied in base. |
| template<int N> |
| void vs_ldpq(const VSeq<N>& v, Register base) { |
| for (int i = 0; i < N; i += 2) { |
| __ ldpq(v[i], v[i+1], Address(base, 32 * i)); |
| } |
| } |
| |
| // load N/2 successive pairs of quadword values from memory in order |
| // into N vector registers of the sequence via the address supplied |
| // in base using post-increment addressing |
| template<int N> |
| void vs_ldpq_post(const VSeq<N>& v, Register base) { |
| static_assert((N & (N - 1)) == 0, "sequence length must be even"); |
| for (int i = 0; i < N; i += 2) { |
| __ ldpq(v[i], v[i+1], __ post(base, 32)); |
| } |
| } |
| |
| // store N successive vector registers of the sequence into N/2 |
| // successive pairs of quadword memory locations via the address |
| // supplied in base using post-increment addressing |
| template<int N> |
| void vs_stpq_post(const VSeq<N>& v, Register base) { |
| static_assert((N & (N - 1)) == 0, "sequence length must be even"); |
| for (int i = 0; i < N; i += 2) { |
| __ stpq(v[i], v[i+1], __ post(base, 32)); |
| } |
| } |
| |
| // load N/2 pairs of quadword values from memory de-interleaved into |
| // N vector registers 2 at a time via the address supplied in base |
| // using post-increment addressing. |
| template<int N> |
| void vs_ld2_post(const VSeq<N>& v, Assembler::SIMD_Arrangement T, Register base) { |
| static_assert((N & (N - 1)) == 0, "sequence length must be even"); |
| for (int i = 0; i < N; i += 2) { |
| __ ld2(v[i], v[i+1], T, __ post(base, 32)); |
| } |
| } |
| |
| // store N vector registers interleaved into N/2 pairs of quadword |
| // memory locations via the address supplied in base using |
| // post-increment addressing. |
| template<int N> |
| void vs_st2_post(const VSeq<N>& v, Assembler::SIMD_Arrangement T, Register base) { |
| static_assert((N & (N - 1)) == 0, "sequence length must be even"); |
| for (int i = 0; i < N; i += 2) { |
| __ st2(v[i], v[i+1], T, __ post(base, 32)); |
| } |
| } |
| |
| // load N quadword values from memory de-interleaved into N vector |
| // registers 3 elements at a time via the address supplied in base. |
| template<int N> |
| void vs_ld3(const VSeq<N>& v, Assembler::SIMD_Arrangement T, Register base) { |
| static_assert(N == ((N / 3) * 3), "sequence length must be multiple of 3"); |
| for (int i = 0; i < N; i += 3) { |
| __ ld3(v[i], v[i+1], v[i+2], T, base); |
| } |
| } |
| |
| // load N quadword values from memory de-interleaved into N vector |
| // registers 3 elements at a time via the address supplied in base |
| // using post-increment addressing. |
| template<int N> |
| void vs_ld3_post(const VSeq<N>& v, Assembler::SIMD_Arrangement T, Register base) { |
| static_assert(N == ((N / 3) * 3), "sequence length must be multiple of 3"); |
| for (int i = 0; i < N; i += 3) { |
| __ ld3(v[i], v[i+1], v[i+2], T, __ post(base, 48)); |
| } |
| } |
| |
| // load N/2 pairs of quadword values from memory into N vector |
| // registers via the address supplied in base with each pair indexed |
| // using the the start offset plus the corresponding entry in the |
| // offsets array |
| template<int N> |
| void vs_ldpq_indexed(const VSeq<N>& v, Register base, int start, int (&offsets)[N/2]) { |
| for (int i = 0; i < N/2; i++) { |
| __ ldpq(v[2*i], v[2*i+1], Address(base, start + offsets[i])); |
| } |
| } |
| |
| // store N vector registers into N/2 pairs of quadword memory |
| // locations via the address supplied in base with each pair indexed |
| // using the the start offset plus the corresponding entry in the |
| // offsets array |
| template<int N> |
| void vs_stpq_indexed(const VSeq<N>& v, Register base, int start, int offsets[N/2]) { |
| for (int i = 0; i < N/2; i++) { |
| __ stpq(v[2*i], v[2*i+1], Address(base, start + offsets[i])); |
| } |
| } |
| |
| // load N single quadword values from memory into N vector registers |
| // via the address supplied in base with each value indexed using |
| // the the start offset plus the corresponding entry in the offsets |
| // array |
| template<int N> |
| void vs_ldr_indexed(const VSeq<N>& v, Assembler::SIMD_RegVariant T, Register base, |
| int start, int (&offsets)[N]) { |
| for (int i = 0; i < N; i++) { |
| __ ldr(v[i], T, Address(base, start + offsets[i])); |
| } |
| } |
| |
| // store N vector registers into N single quadword memory locations |
| // via the address supplied in base with each value indexed using |
| // the the start offset plus the corresponding entry in the offsets |
| // array |
| template<int N> |
| void vs_str_indexed(const VSeq<N>& v, Assembler::SIMD_RegVariant T, Register base, |
| int start, int (&offsets)[N]) { |
| for (int i = 0; i < N; i++) { |
| __ str(v[i], T, Address(base, start + offsets[i])); |
| } |
| } |
| |
| // load N/2 pairs of quadword values from memory de-interleaved into |
| // N vector registers 2 at a time via the address supplied in base |
| // with each pair indexed using the the start offset plus the |
| // corresponding entry in the offsets array |
| template<int N> |
| void vs_ld2_indexed(const VSeq<N>& v, Assembler::SIMD_Arrangement T, Register base, |
| Register tmp, int start, int (&offsets)[N/2]) { |
| for (int i = 0; i < N/2; i++) { |
| __ add(tmp, base, start + offsets[i]); |
| __ ld2(v[2*i], v[2*i+1], T, tmp); |
| } |
| } |
| |
| // store N vector registers 2 at a time interleaved into N/2 pairs |
| // of quadword memory locations via the address supplied in base |
| // with each pair indexed using the the start offset plus the |
| // corresponding entry in the offsets array |
| template<int N> |
| void vs_st2_indexed(const VSeq<N>& v, Assembler::SIMD_Arrangement T, Register base, |
| Register tmp, int start, int (&offsets)[N/2]) { |
| for (int i = 0; i < N/2; i++) { |
| __ add(tmp, base, start + offsets[i]); |
| __ st2(v[2*i], v[2*i+1], T, tmp); |
| } |
| } |
| |
| // Helper routines for various flavours of Montgomery multiply |
| |
| // Perform 16 32-bit (4x4S) or 32 16-bit (4 x 8H) Montgomery |
| // multiplications in parallel |
| // |
| |
| // See the montMul() method of the sun.security.provider.ML_DSA |
| // class. |
| // |
| // Computes 4x4S results or 8x8H results |
| // a = b * c * 2^MONT_R_BITS mod MONT_Q |
| // Inputs: vb, vc - 4x4S or 4x8H vector register sequences |
| // vq - 2x4S or 2x8H constants <MONT_Q, MONT_Q_INV_MOD_R> |
| // Temps: vtmp - 4x4S or 4x8H vector sequence trashed after call |
| // Outputs: va - 4x4S or 4x8H vector register sequences |
| // vb, vc, vtmp and vq must all be disjoint |
| // va must be disjoint from all other inputs/temps or must equal vc |
| // va must have a non-zero delta i.e. it must not be a constant vseq. |
| // n.b. MONT_R_BITS is 16 or 32, so the right shift by it is implicit. |
| void vs_montmul4(const VSeq<4>& va, const VSeq<4>& vb, const VSeq<4>& vc, |
| Assembler::SIMD_Arrangement T, |
| const VSeq<4>& vtmp, const VSeq<2>& vq) { |
| assert (T == __ T4S || T == __ T8H, "invalid arrangement for montmul"); |
| assert(vs_disjoint(vb, vc), "vb and vc overlap"); |
| assert(vs_disjoint(vb, vq), "vb and vq overlap"); |
| assert(vs_disjoint(vb, vtmp), "vb and vtmp overlap"); |
| |
| assert(vs_disjoint(vc, vq), "vc and vq overlap"); |
| assert(vs_disjoint(vc, vtmp), "vc and vtmp overlap"); |
| |
| assert(vs_disjoint(vq, vtmp), "vq and vtmp overlap"); |
| |
| assert(vs_disjoint(va, vc) || vs_same(va, vc), "va and vc neither disjoint nor equal"); |
| assert(vs_disjoint(va, vb), "va and vb overlap"); |
| assert(vs_disjoint(va, vq), "va and vq overlap"); |
| assert(vs_disjoint(va, vtmp), "va and vtmp overlap"); |
| assert(!va.is_constant(), "output vector must identify 4 different registers"); |
| |
| // schedule 4 streams of instructions across the vector sequences |
| for (int i = 0; i < 4; i++) { |
| __ sqdmulh(vtmp[i], T, vb[i], vc[i]); // aHigh = hi32(2 * b * c) |
| __ mulv(va[i], T, vb[i], vc[i]); // aLow = lo32(b * c) |
| } |
| |
| for (int i = 0; i < 4; i++) { |
| __ mulv(va[i], T, va[i], vq[0]); // m = aLow * qinv |
| } |
| |
| for (int i = 0; i < 4; i++) { |
| __ sqdmulh(va[i], T, va[i], vq[1]); // n = hi32(2 * m * q) |
| } |
| |
| for (int i = 0; i < 4; i++) { |
| __ shsubv(va[i], T, vtmp[i], va[i]); // a = (aHigh - n) / 2 |
| } |
| } |
| |
| // Perform 8 32-bit (4x4S) or 16 16-bit (2 x 8H) Montgomery |
| // multiplications in parallel |
| // |
| |
| // See the montMul() method of the sun.security.provider.ML_DSA |
| // class. |
| // |
| // Computes 4x4S results or 8x8H results |
| // a = b * c * 2^MONT_R_BITS mod MONT_Q |
| // Inputs: vb, vc - 4x4S or 4x8H vector register sequences |
| // vq - 2x4S or 2x8H constants <MONT_Q, MONT_Q_INV_MOD_R> |
| // Temps: vtmp - 4x4S or 4x8H vector sequence trashed after call |
| // Outputs: va - 4x4S or 4x8H vector register sequences |
| // vb, vc, vtmp and vq must all be disjoint |
| // va must be disjoint from all other inputs/temps or must equal vc |
| // va must have a non-zero delta i.e. it must not be a constant vseq. |
| // n.b. MONT_R_BITS is 16 or 32, so the right shift by it is implicit. |
| void vs_montmul2(const VSeq<2>& va, const VSeq<2>& vb, const VSeq<2>& vc, |
| Assembler::SIMD_Arrangement T, |
| const VSeq<2>& vtmp, const VSeq<2>& vq) { |
| assert (T == __ T4S || T == __ T8H, "invalid arrangement for montmul"); |
| assert(vs_disjoint(vb, vc), "vb and vc overlap"); |
| assert(vs_disjoint(vb, vq), "vb and vq overlap"); |
| assert(vs_disjoint(vb, vtmp), "vb and vtmp overlap"); |
| |
| assert(vs_disjoint(vc, vq), "vc and vq overlap"); |
| assert(vs_disjoint(vc, vtmp), "vc and vtmp overlap"); |
| |
| assert(vs_disjoint(vq, vtmp), "vq and vtmp overlap"); |
| |
| assert(vs_disjoint(va, vc) || vs_same(va, vc), "va and vc neither disjoint nor equal"); |
| assert(vs_disjoint(va, vb), "va and vb overlap"); |
| assert(vs_disjoint(va, vq), "va and vq overlap"); |
| assert(vs_disjoint(va, vtmp), "va and vtmp overlap"); |
| assert(!va.is_constant(), "output vector must identify 2 different registers"); |
| |
| // schedule 2 streams of instructions across the vector sequences |
| for (int i = 0; i < 2; i++) { |
| __ sqdmulh(vtmp[i], T, vb[i], vc[i]); // aHigh = hi32(2 * b * c) |
| __ mulv(va[i], T, vb[i], vc[i]); // aLow = lo32(b * c) |
| } |
| |
| for (int i = 0; i < 2; i++) { |
| __ mulv(va[i], T, va[i], vq[0]); // m = aLow * qinv |
| } |
| |
| for (int i = 0; i < 2; i++) { |
| __ sqdmulh(va[i], T, va[i], vq[1]); // n = hi32(2 * m * q) |
| } |
| |
| for (int i = 0; i < 2; i++) { |
| __ shsubv(va[i], T, vtmp[i], va[i]); // a = (aHigh - n) / 2 |
| } |
| } |
| |
| // Perform 16 16-bit Montgomery multiplications in parallel. |
| void kyber_montmul16(const VSeq<2>& va, const VSeq<2>& vb, const VSeq<2>& vc, |
| const VSeq<2>& vtmp, const VSeq<2>& vq) { |
| // Use the helper routine to schedule a 2x8H Montgomery multiply. |
| // It will assert that the register use is valid |
| vs_montmul2(va, vb, vc, __ T8H, vtmp, vq); |
| } |
| |
| // Perform 32 16-bit Montgomery multiplications in parallel. |
| void kyber_montmul32(const VSeq<4>& va, const VSeq<4>& vb, const VSeq<4>& vc, |
| const VSeq<4>& vtmp, const VSeq<2>& vq) { |
| // Use the helper routine to schedule a 4x8H Montgomery multiply. |
| // It will assert that the register use is valid |
| vs_montmul4(va, vb, vc, __ T8H, vtmp, vq); |
| } |
| |
| // Perform 64 16-bit Montgomery multiplications in parallel. |
| void kyber_montmul64(const VSeq<8>& va, const VSeq<8>& vb, const VSeq<8>& vc, |
| const VSeq<4>& vtmp, const VSeq<2>& vq) { |
| // Schedule two successive 4x8H multiplies via the montmul helper |
| // on the front and back halves of va, vb and vc. The helper will |
| // assert that the register use has no overlap conflicts on each |
| // individual call but we also need to ensure that the necessary |
| // disjoint/equality constraints are met across both calls. |
| |
| // vb, vc, vtmp and vq must be disjoint. va must either be |
| // disjoint from all other registers or equal vc |
| |
| assert(vs_disjoint(vb, vc), "vb and vc overlap"); |
| assert(vs_disjoint(vb, vq), "vb and vq overlap"); |
| assert(vs_disjoint(vb, vtmp), "vb and vtmp overlap"); |
| |
| assert(vs_disjoint(vc, vq), "vc and vq overlap"); |
| assert(vs_disjoint(vc, vtmp), "vc and vtmp overlap"); |
| |
| assert(vs_disjoint(vq, vtmp), "vq and vtmp overlap"); |
| |
| assert(vs_disjoint(va, vc) || vs_same(va, vc), "va and vc neither disjoint nor equal"); |
| assert(vs_disjoint(va, vb), "va and vb overlap"); |
| assert(vs_disjoint(va, vq), "va and vq overlap"); |
| assert(vs_disjoint(va, vtmp), "va and vtmp overlap"); |
| |
| // we multiply the front and back halves of each sequence 4 at a |
| // time because |
| // |
| // 1) we are currently only able to get 4-way instruction |
| // parallelism at best |
| // |
| // 2) we need registers for the constants in vq and temporary |
| // scratch registers to hold intermediate results so vtmp can only |
| // be a VSeq<4> which means we only have 4 scratch slots |
| |
| vs_montmul4(vs_front(va), vs_front(vb), vs_front(vc), __ T8H, vtmp, vq); |
| vs_montmul4(vs_back(va), vs_back(vb), vs_back(vc), __ T8H, vtmp, vq); |
| } |
| |
| void kyber_montmul32_sub_add(const VSeq<4>& va0, const VSeq<4>& va1, |
| const VSeq<4>& vc, |
| const VSeq<4>& vtmp, |
| const VSeq<2>& vq) { |
| // compute a = montmul(a1, c) |
| kyber_montmul32(vc, va1, vc, vtmp, vq); |
| // ouptut a1 = a0 - a |
| vs_subv(va1, __ T8H, va0, vc); |
| // and a0 = a0 + a |
| vs_addv(va0, __ T8H, va0, vc); |
| } |
| |
| void kyber_sub_add_montmul32(const VSeq<4>& va0, const VSeq<4>& va1, |
| const VSeq<4>& vb, |
| const VSeq<4>& vtmp1, |
| const VSeq<4>& vtmp2, |
| const VSeq<2>& vq) { |
| // compute c = a0 - a1 |
| vs_subv(vtmp1, __ T8H, va0, va1); |
| // output a0 = a0 + a1 |
| vs_addv(va0, __ T8H, va0, va1); |
| // output a1 = b montmul c |
| kyber_montmul32(va1, vtmp1, vb, vtmp2, vq); |
| } |
| |
| void load64shorts(const VSeq<8>& v, Register shorts) { |
| vs_ldpq_post(v, shorts); |
| } |
| |
| void load32shorts(const VSeq<4>& v, Register shorts) { |
| vs_ldpq_post(v, shorts); |
| } |
| |
| void store64shorts(VSeq<8> v, Register tmpAddr) { |
| vs_stpq_post(v, tmpAddr); |
| } |
| |
| // Kyber NTT function. |
| // Implements |
| // static int implKyberNtt(short[] poly, short[] ntt_zetas) {} |
| // |
| // coeffs (short[256]) = c_rarg0 |
| // ntt_zetas (short[256]) = c_rarg1 |
| address generate_kyberNtt() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyberNtt_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register coeffs = c_rarg0; |
| const Register zetas = c_rarg1; |
| |
| const Register kyberConsts = r10; |
| const Register tmpAddr = r11; |
| |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x8H inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| |
| __ lea(kyberConsts, ExternalAddress((address) StubRoutines::aarch64::_kyberConsts)); |
| // load the montmul constants |
| vs_ldpq(vq, kyberConsts); |
| |
| // Each level corresponds to an iteration of the outermost loop of the |
| // Java method seilerNTT(int[] coeffs). There are some differences |
| // from what is done in the seilerNTT() method, though: |
| // 1. The computation is using 16-bit signed values, we do not convert them |
| // to ints here. |
| // 2. The zetas are delivered in a bigger array, 128 zetas are stored in |
| // this array for each level, it is easier that way to fill up the vector |
| // registers. |
| // 3. In the seilerNTT() method we use R = 2^20 for the Montgomery |
| // multiplications (this is because that way there should not be any |
| // overflow during the inverse NTT computation), here we usr R = 2^16 so |
| // that we can use the 16-bit arithmetic in the vector unit. |
| // |
| // On each level, we fill up the vector registers in such a way that the |
| // array elements that need to be multiplied by the zetas go into one |
| // set of vector registers while the corresponding ones that don't need to |
| // be multiplied, go into another set. |
| // We can do 32 Montgomery multiplications in parallel, using 12 vector |
| // registers interleaving the steps of 4 identical computations, |
| // each done on 8 16-bit values per register. |
| |
| // At levels 0-3 the coefficients multiplied by or added/subtracted |
| // to the zetas occur in discrete blocks whose size is some multiple |
| // of 32. |
| |
| // level 0 |
| __ add(tmpAddr, coeffs, 256); |
| load64shorts(vs1, tmpAddr); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 0); |
| load64shorts(vs1, tmpAddr); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 0); |
| vs_stpq_post(vs1, tmpAddr); |
| __ add(tmpAddr, coeffs, 256); |
| vs_stpq_post(vs3, tmpAddr); |
| // restore montmul constants |
| vs_ldpq(vq, kyberConsts); |
| load64shorts(vs1, tmpAddr); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 128); |
| load64shorts(vs1, tmpAddr); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 128); |
| store64shorts(vs1, tmpAddr); |
| __ add(tmpAddr, coeffs, 384); |
| store64shorts(vs3, tmpAddr); |
| |
| // level 1 |
| // restore montmul constants |
| vs_ldpq(vq, kyberConsts); |
| __ add(tmpAddr, coeffs, 128); |
| load64shorts(vs1, tmpAddr); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 0); |
| load64shorts(vs1, tmpAddr); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 0); |
| store64shorts(vs1, tmpAddr); |
| store64shorts(vs3, tmpAddr); |
| vs_ldpq(vq, kyberConsts); |
| __ add(tmpAddr, coeffs, 384); |
| load64shorts(vs1, tmpAddr); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 256); |
| load64shorts(vs1, tmpAddr); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 256); |
| store64shorts(vs1, tmpAddr); |
| store64shorts(vs3, tmpAddr); |
| |
| // level 2 |
| vs_ldpq(vq, kyberConsts); |
| int offsets1[4] = { 0, 32, 128, 160 }; |
| vs_ldpq_indexed(vs1, coeffs, 64, offsets1); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_ldpq_indexed(vs1, coeffs, 0, offsets1); |
| // kyber_subv_addv64(); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 0); |
| vs_stpq_post(vs_front(vs1), tmpAddr); |
| vs_stpq_post(vs_front(vs3), tmpAddr); |
| vs_stpq_post(vs_back(vs1), tmpAddr); |
| vs_stpq_post(vs_back(vs3), tmpAddr); |
| vs_ldpq(vq, kyberConsts); |
| vs_ldpq_indexed(vs1, tmpAddr, 64, offsets1); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_ldpq_indexed(vs1, coeffs, 256, offsets1); |
| // kyber_subv_addv64(); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 256); |
| vs_stpq_post(vs_front(vs1), tmpAddr); |
| vs_stpq_post(vs_front(vs3), tmpAddr); |
| vs_stpq_post(vs_back(vs1), tmpAddr); |
| vs_stpq_post(vs_back(vs3), tmpAddr); |
| |
| // level 3 |
| vs_ldpq(vq, kyberConsts); |
| int offsets2[4] = { 0, 64, 128, 192 }; |
| vs_ldpq_indexed(vs1, coeffs, 32, offsets2); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_ldpq_indexed(vs1, coeffs, 0, offsets2); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| vs_stpq_indexed(vs1, coeffs, 0, offsets2); |
| vs_stpq_indexed(vs3, coeffs, 32, offsets2); |
| |
| vs_ldpq(vq, kyberConsts); |
| vs_ldpq_indexed(vs1, coeffs, 256 + 32, offsets2); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_ldpq_indexed(vs1, coeffs, 256, offsets2); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| vs_stpq_indexed(vs1, coeffs, 256, offsets2); |
| vs_stpq_indexed(vs3, coeffs, 256 + 32, offsets2); |
| |
| // level 4 |
| // At level 4 coefficients occur in 8 discrete blocks of size 16 |
| // so they are loaded using employing an ldr at 8 distinct offsets. |
| |
| vs_ldpq(vq, kyberConsts); |
| int offsets3[8] = { 0, 32, 64, 96, 128, 160, 192, 224 }; |
| vs_ldr_indexed(vs1, __ Q, coeffs, 16, offsets3); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_ldr_indexed(vs1, __ Q, coeffs, 0, offsets3); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| vs_str_indexed(vs1, __ Q, coeffs, 0, offsets3); |
| vs_str_indexed(vs3, __ Q, coeffs, 16, offsets3); |
| |
| vs_ldpq(vq, kyberConsts); |
| vs_ldr_indexed(vs1, __ Q, coeffs, 256 + 16, offsets3); |
| load64shorts(vs2, zetas); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_ldr_indexed(vs1, __ Q, coeffs, 256, offsets3); |
| vs_subv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_addv(vs1, __ T8H, vs1, vs2); |
| vs_str_indexed(vs1, __ Q, coeffs, 256, offsets3); |
| vs_str_indexed(vs3, __ Q, coeffs, 256 + 16, offsets3); |
| |
| // level 5 |
| // At level 5 related coefficients occur in discrete blocks of size 8 so |
| // need to be loaded interleaved using an ld2 operation with arrangement 2D. |
| |
| vs_ldpq(vq, kyberConsts); |
| int offsets4[4] = { 0, 32, 64, 96 }; |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 0, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 0, offsets4); |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 128, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 128, offsets4); |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 256, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 256, offsets4); |
| |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 384, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 384, offsets4); |
| |
| // level 6 |
| // At level 6 related coefficients occur in discrete blocks of size 4 so |
| // need to be loaded interleaved using an ld2 operation with arrangement 4S. |
| |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 0, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 0, offsets4); |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 128, offsets4); |
| // __ ldpq(v18, v19, __ post(zetas, 32)); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 128, offsets4); |
| |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 256, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 256, offsets4); |
| |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 384, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_montmul32_sub_add(vs_even(vs1), vs_odd(vs1), vs_front(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 384, offsets4); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Kyber Inverse NTT function |
| // Implements |
| // static int implKyberInverseNtt(short[] poly, short[] zetas) {} |
| // |
| // coeffs (short[256]) = c_rarg0 |
| // ntt_zetas (short[256]) = c_rarg1 |
| address generate_kyberInverseNtt() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyberInverseNtt_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register coeffs = c_rarg0; |
| const Register zetas = c_rarg1; |
| |
| const Register kyberConsts = r10; |
| const Register tmpAddr = r11; |
| const Register tmpAddr2 = c_rarg2; |
| |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x8H inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| |
| __ lea(kyberConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_kyberConsts)); |
| |
| // level 0 |
| // At level 0 related coefficients occur in discrete blocks of size 4 so |
| // need to be loaded interleaved using an ld2 operation with arrangement 4S. |
| |
| vs_ldpq(vq, kyberConsts); |
| int offsets4[4] = { 0, 32, 64, 96 }; |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 0, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 0, offsets4); |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 128, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 128, offsets4); |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 256, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 256, offsets4); |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, 384, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, 384, offsets4); |
| |
| // level 1 |
| // At level 1 related coefficients occur in discrete blocks of size 8 so |
| // need to be loaded interleaved using an ld2 operation with arrangement 2D. |
| |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 0, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 0, offsets4); |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 128, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 128, offsets4); |
| |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 256, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 256, offsets4); |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, 384, offsets4); |
| load32shorts(vs_front(vs2), zetas); |
| kyber_sub_add_montmul32(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, 384, offsets4); |
| |
| // level 2 |
| // At level 2 coefficients occur in 8 discrete blocks of size 16 |
| // so they are loaded using employing an ldr at 8 distinct offsets. |
| |
| int offsets3[8] = { 0, 32, 64, 96, 128, 160, 192, 224 }; |
| vs_ldr_indexed(vs1, __ Q, coeffs, 0, offsets3); |
| vs_ldr_indexed(vs2, __ Q, coeffs, 16, offsets3); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| vs_str_indexed(vs3, __ Q, coeffs, 0, offsets3); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_str_indexed(vs2, __ Q, coeffs, 16, offsets3); |
| |
| vs_ldr_indexed(vs1, __ Q, coeffs, 256, offsets3); |
| vs_ldr_indexed(vs2, __ Q, coeffs, 256 + 16, offsets3); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| vs_str_indexed(vs3, __ Q, coeffs, 256, offsets3); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_str_indexed(vs2, __ Q, coeffs, 256 + 16, offsets3); |
| |
| // Barrett reduction at indexes where overflow may happen |
| |
| // load q and the multiplier for the Barrett reduction |
| __ add(tmpAddr, kyberConsts, 16); |
| vs_ldpq(vq, tmpAddr); |
| |
| VSeq<8> vq1 = VSeq<8>(vq[0], 0); // 2 constant 8 sequences |
| VSeq<8> vq2 = VSeq<8>(vq[1], 0); // for above two kyber constants |
| VSeq<8> vq3 = VSeq<8>(v29, 0); // 3rd sequence for const montmul |
| vs_ldr_indexed(vs1, __ Q, coeffs, 0, offsets3); |
| vs_sqdmulh(vs2, __ T8H, vs1, vq2); |
| vs_sshr(vs2, __ T8H, vs2, 11); |
| vs_mlsv(vs1, __ T8H, vs2, vq1); |
| vs_str_indexed(vs1, __ Q, coeffs, 0, offsets3); |
| vs_ldr_indexed(vs1, __ Q, coeffs, 256, offsets3); |
| vs_sqdmulh(vs2, __ T8H, vs1, vq2); |
| vs_sshr(vs2, __ T8H, vs2, 11); |
| vs_mlsv(vs1, __ T8H, vs2, vq1); |
| vs_str_indexed(vs1, __ Q, coeffs, 256, offsets3); |
| |
| // level 3 |
| // From level 3 upwards coefficients occur in discrete blocks whose size is |
| // some multiple of 32 so can be loaded using ldpq and suitable indexes. |
| |
| int offsets2[4] = { 0, 64, 128, 192 }; |
| vs_ldpq_indexed(vs1, coeffs, 0, offsets2); |
| vs_ldpq_indexed(vs2, coeffs, 32, offsets2); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| vs_stpq_indexed(vs3, coeffs, 0, offsets2); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_stpq_indexed(vs2, coeffs, 32, offsets2); |
| |
| vs_ldpq_indexed(vs1, coeffs, 256, offsets2); |
| vs_ldpq_indexed(vs2, coeffs, 256 + 32, offsets2); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| vs_stpq_indexed(vs3, coeffs, 256, offsets2); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_stpq_indexed(vs2, coeffs, 256 + 32, offsets2); |
| |
| // level 4 |
| |
| int offsets1[4] = { 0, 32, 128, 160 }; |
| vs_ldpq_indexed(vs1, coeffs, 0, offsets1); |
| vs_ldpq_indexed(vs2, coeffs, 64, offsets1); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| vs_stpq_indexed(vs3, coeffs, 0, offsets1); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_stpq_indexed(vs2, coeffs, 64, offsets1); |
| |
| vs_ldpq_indexed(vs1, coeffs, 256, offsets1); |
| vs_ldpq_indexed(vs2, coeffs, 256 + 64, offsets1); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| vs_stpq_indexed(vs3, coeffs, 256, offsets1); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| vs_stpq_indexed(vs2, coeffs, 256 + 64, offsets1); |
| |
| // level 5 |
| |
| __ add(tmpAddr, coeffs, 0); |
| load64shorts(vs1, tmpAddr); |
| __ add(tmpAddr, coeffs, 128); |
| load64shorts(vs2, tmpAddr); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 0); |
| store64shorts(vs3, tmpAddr); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 128); |
| store64shorts(vs2, tmpAddr); |
| |
| load64shorts(vs1, tmpAddr); |
| __ add(tmpAddr, coeffs, 384); |
| load64shorts(vs2, tmpAddr); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 256); |
| store64shorts(vs3, tmpAddr); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 384); |
| store64shorts(vs2, tmpAddr); |
| |
| // Barrett reduction at indexes where overflow may happen |
| |
| // load q and the multiplier for the Barrett reduction |
| __ add(tmpAddr, kyberConsts, 16); |
| vs_ldpq(vq, tmpAddr); |
| |
| int offsets0[2] = { 0, 256 }; |
| vs_ldpq_indexed(vs_front(vs1), coeffs, 0, offsets0); |
| vs_sqdmulh(vs2, __ T8H, vs1, vq2); |
| vs_sshr(vs2, __ T8H, vs2, 11); |
| vs_mlsv(vs1, __ T8H, vs2, vq1); |
| vs_stpq_indexed(vs_front(vs1), coeffs, 0, offsets0); |
| |
| // level 6 |
| |
| __ add(tmpAddr, coeffs, 0); |
| load64shorts(vs1, tmpAddr); |
| __ add(tmpAddr, coeffs, 256); |
| load64shorts(vs2, tmpAddr); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 0); |
| store64shorts(vs3, tmpAddr); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 256); |
| store64shorts(vs2, tmpAddr); |
| |
| __ add(tmpAddr, coeffs, 128); |
| load64shorts(vs1, tmpAddr); |
| __ add(tmpAddr, coeffs, 384); |
| load64shorts(vs2, tmpAddr); |
| vs_addv(vs3, __ T8H, vs1, vs2); // n.b. trashes vq |
| vs_subv(vs1, __ T8H, vs1, vs2); |
| __ add(tmpAddr, coeffs, 128); |
| store64shorts(vs3, tmpAddr); |
| load64shorts(vs2, zetas); |
| vs_ldpq(vq, kyberConsts); |
| kyber_montmul64(vs2, vs1, vs2, vtmp, vq); |
| __ add(tmpAddr, coeffs, 384); |
| store64shorts(vs2, tmpAddr); |
| |
| // multiply by 2^-n |
| |
| // load toMont(2^-n mod q) |
| __ add(tmpAddr, kyberConsts, 48); |
| __ ldr(v29, __ Q, tmpAddr); |
| |
| vs_ldpq(vq, kyberConsts); |
| __ add(tmpAddr, coeffs, 0); |
| load64shorts(vs1, tmpAddr); |
| kyber_montmul64(vs2, vs1, vq3, vtmp, vq); |
| __ add(tmpAddr, coeffs, 0); |
| store64shorts(vs2, tmpAddr); |
| |
| // now tmpAddr contains coeffs + 128 because store64shorts adjusted it so |
| load64shorts(vs1, tmpAddr); |
| kyber_montmul64(vs2, vs1, vq3, vtmp, vq); |
| __ add(tmpAddr, coeffs, 128); |
| store64shorts(vs2, tmpAddr); |
| |
| // now tmpAddr contains coeffs + 256 |
| load64shorts(vs1, tmpAddr); |
| kyber_montmul64(vs2, vs1, vq3, vtmp, vq); |
| __ add(tmpAddr, coeffs, 256); |
| store64shorts(vs2, tmpAddr); |
| |
| // now tmpAddr contains coeffs + 384 |
| load64shorts(vs1, tmpAddr); |
| kyber_montmul64(vs2, vs1, vq3, vtmp, vq); |
| __ add(tmpAddr, coeffs, 384); |
| store64shorts(vs2, tmpAddr); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Kyber multiply polynomials in the NTT domain. |
| // Implements |
| // static int implKyberNttMult( |
| // short[] result, short[] ntta, short[] nttb, short[] zetas) {} |
| // |
| // result (short[256]) = c_rarg0 |
| // ntta (short[256]) = c_rarg1 |
| // nttb (short[256]) = c_rarg2 |
| // zetas (short[128]) = c_rarg3 |
| address generate_kyberNttMult() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyberNttMult_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register result = c_rarg0; |
| const Register ntta = c_rarg1; |
| const Register nttb = c_rarg2; |
| const Register zetas = c_rarg3; |
| |
| const Register kyberConsts = r10; |
| const Register limit = r11; |
| |
| VSeq<4> vs1(0), vs2(4); // 4 sets of 8x8H inputs/outputs/tmps |
| VSeq<4> vs3(16), vs4(20); |
| VSeq<2> vq(30); // pair of constants for montmul: q, qinv |
| VSeq<2> vz(28); // pair of zetas |
| VSeq<4> vc(27, 0); // constant sequence for montmul: montRSquareModQ |
| |
| __ lea(kyberConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_kyberConsts)); |
| |
| Label kyberNttMult_loop; |
| |
| __ add(limit, result, 512); |
| |
| // load q and qinv |
| vs_ldpq(vq, kyberConsts); |
| |
| // load R^2 mod q (to convert back from Montgomery representation) |
| __ add(kyberConsts, kyberConsts, 64); |
| __ ldr(v27, __ Q, kyberConsts); |
| |
| __ BIND(kyberNttMult_loop); |
| |
| // load 16 zetas |
| vs_ldpq_post(vz, zetas); |
| |
| // load 2 sets of 32 coefficients from the two input arrays |
| // interleaved as shorts. i.e. pairs of shorts adjacent in memory |
| // are striped across pairs of vector registers |
| vs_ld2_post(vs_front(vs1), __ T8H, ntta); // <a0, a1> x 8H |
| vs_ld2_post(vs_back(vs1), __ T8H, nttb); // <b0, b1> x 8H |
| vs_ld2_post(vs_front(vs4), __ T8H, ntta); // <a2, a3> x 8H |
| vs_ld2_post(vs_back(vs4), __ T8H, nttb); // <b2, b3> x 8H |
| |
| // compute 4 montmul cross-products for pairs (a0,a1) and (b0,b1) |
| // i.e. montmul the first and second halves of vs1 in order and |
| // then with one sequence reversed storing the two results in vs3 |
| // |
| // vs3[0] <- montmul(a0, b0) |
| // vs3[1] <- montmul(a1, b1) |
| // vs3[2] <- montmul(a0, b1) |
| // vs3[3] <- montmul(a1, b0) |
| kyber_montmul16(vs_front(vs3), vs_front(vs1), vs_back(vs1), vs_front(vs2), vq); |
| kyber_montmul16(vs_back(vs3), |
| vs_front(vs1), vs_reverse(vs_back(vs1)), vs_back(vs2), vq); |
| |
| // compute 4 montmul cross-products for pairs (a2,a3) and (b2,b3) |
| // i.e. montmul the first and second halves of vs4 in order and |
| // then with one sequence reversed storing the two results in vs1 |
| // |
| // vs1[0] <- montmul(a2, b2) |
| // vs1[1] <- montmul(a3, b3) |
| // vs1[2] <- montmul(a2, b3) |
| // vs1[3] <- montmul(a3, b2) |
| kyber_montmul16(vs_front(vs1), vs_front(vs4), vs_back(vs4), vs_front(vs2), vq); |
| kyber_montmul16(vs_back(vs1), |
| vs_front(vs4), vs_reverse(vs_back(vs4)), vs_back(vs2), vq); |
| |
| // montmul result 2 of each cross-product i.e. (a1*b1, a3*b3) by a zeta. |
| // We can schedule two montmuls at a time if we use a suitable vector |
| // sequence <vs3[1], vs1[1]>. |
| int delta = vs1[1]->encoding() - vs3[1]->encoding(); |
| VSeq<2> vs5(vs3[1], delta); |
| |
| // vs3[1] <- montmul(montmul(a1, b1), z0) |
| // vs1[1] <- montmul(montmul(a3, b3), z1) |
| kyber_montmul16(vs5, vz, vs5, vs_front(vs2), vq); |
| |
| // add results in pairs storing in vs3 |
| // vs3[0] <- montmul(a0, b0) + montmul(montmul(a1, b1), z0); |
| // vs3[1] <- montmul(a0, b1) + montmul(a1, b0); |
| vs_addv(vs_front(vs3), __ T8H, vs_even(vs3), vs_odd(vs3)); |
| |
| // vs3[2] <- montmul(a2, b2) + montmul(montmul(a3, b3), z1); |
| // vs3[3] <- montmul(a2, b3) + montmul(a3, b2); |
| vs_addv(vs_back(vs3), __ T8H, vs_even(vs1), vs_odd(vs1)); |
| |
| // vs1 <- montmul(vs3, montRSquareModQ) |
| kyber_montmul32(vs1, vs3, vc, vs2, vq); |
| |
| // store back the two pairs of result vectors de-interleaved as 8H elements |
| // i.e. storing each pairs of shorts striped across a register pair adjacent |
| // in memory |
| vs_st2_post(vs1, __ T8H, result); |
| |
| __ cmp(result, limit); |
| __ br(Assembler::NE, kyberNttMult_loop); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Kyber add 2 polynomials. |
| // Implements |
| // static int implKyberAddPoly(short[] result, short[] a, short[] b) {} |
| // |
| // result (short[256]) = c_rarg0 |
| // a (short[256]) = c_rarg1 |
| // b (short[256]) = c_rarg2 |
| address generate_kyberAddPoly_2() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyberAddPoly_2_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register result = c_rarg0; |
| const Register a = c_rarg1; |
| const Register b = c_rarg2; |
| |
| const Register kyberConsts = r11; |
| |
| // We sum 256 sets of values in total i.e. 32 x 8H quadwords. |
| // So, we can load, add and store the data in 3 groups of 11, |
| // 11 and 10 at a time i.e. we need to map sets of 10 or 11 |
| // registers. A further constraint is that the mapping needs |
| // to skip callee saves. So, we allocate the register |
| // sequences using two 8 sequences, two 2 sequences and two |
| // single registers. |
| VSeq<8> vs1_1(0); |
| VSeq<2> vs1_2(16); |
| FloatRegister vs1_3 = v28; |
| VSeq<8> vs2_1(18); |
| VSeq<2> vs2_2(26); |
| FloatRegister vs2_3 = v29; |
| |
| // two constant vector sequences |
| VSeq<8> vc_1(31, 0); |
| VSeq<2> vc_2(31, 0); |
| |
| FloatRegister vc_3 = v31; |
| __ lea(kyberConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_kyberConsts)); |
| |
| __ ldr(vc_3, __ Q, Address(kyberConsts, 16)); // q |
| for (int i = 0; i < 3; i++) { |
| // load 80 or 88 values from a into vs1_1/2/3 |
| vs_ldpq_post(vs1_1, a); |
| vs_ldpq_post(vs1_2, a); |
| if (i < 2) { |
| __ ldr(vs1_3, __ Q, __ post(a, 16)); |
| } |
| // load 80 or 88 values from b into vs2_1/2/3 |
| vs_ldpq_post(vs2_1, b); |
| vs_ldpq_post(vs2_2, b); |
| if (i < 2) { |
| __ ldr(vs2_3, __ Q, __ post(b, 16)); |
| } |
| // sum 80 or 88 values across vs1 and vs2 into vs1 |
| vs_addv(vs1_1, __ T8H, vs1_1, vs2_1); |
| vs_addv(vs1_2, __ T8H, vs1_2, vs2_2); |
| if (i < 2) { |
| __ addv(vs1_3, __ T8H, vs1_3, vs2_3); |
| } |
| // add constant to all 80 or 88 results |
| vs_addv(vs1_1, __ T8H, vs1_1, vc_1); |
| vs_addv(vs1_2, __ T8H, vs1_2, vc_2); |
| if (i < 2) { |
| __ addv(vs1_3, __ T8H, vs1_3, vc_3); |
| } |
| // store 80 or 88 values |
| vs_stpq_post(vs1_1, result); |
| vs_stpq_post(vs1_2, result); |
| if (i < 2) { |
| __ str(vs1_3, __ Q, __ post(result, 16)); |
| } |
| } |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Kyber add 3 polynomials. |
| // Implements |
| // static int implKyberAddPoly(short[] result, short[] a, short[] b, short[] c) {} |
| // |
| // result (short[256]) = c_rarg0 |
| // a (short[256]) = c_rarg1 |
| // b (short[256]) = c_rarg2 |
| // c (short[256]) = c_rarg3 |
| address generate_kyberAddPoly_3() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyberAddPoly_3_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register result = c_rarg0; |
| const Register a = c_rarg1; |
| const Register b = c_rarg2; |
| const Register c = c_rarg3; |
| |
| const Register kyberConsts = r11; |
| |
| // As above we sum 256 sets of values in total i.e. 32 x 8H |
| // quadwords. So, we can load, add and store the data in 3 |
| // groups of 11, 11 and 10 at a time i.e. we need to map sets |
| // of 10 or 11 registers. A further constraint is that the |
| // mapping needs to skip callee saves. So, we allocate the |
| // register sequences using two 8 sequences, two 2 sequences |
| // and two single registers. |
| VSeq<8> vs1_1(0); |
| VSeq<2> vs1_2(16); |
| FloatRegister vs1_3 = v28; |
| VSeq<8> vs2_1(18); |
| VSeq<2> vs2_2(26); |
| FloatRegister vs2_3 = v29; |
| |
| // two constant vector sequences |
| VSeq<8> vc_1(31, 0); |
| VSeq<2> vc_2(31, 0); |
| |
| FloatRegister vc_3 = v31; |
| |
| __ lea(kyberConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_kyberConsts)); |
| |
| __ ldr(vc_3, __ Q, Address(kyberConsts, 16)); // q |
| for (int i = 0; i < 3; i++) { |
| // load 80 or 88 values from a into vs1_1/2/3 |
| vs_ldpq_post(vs1_1, a); |
| vs_ldpq_post(vs1_2, a); |
| if (i < 2) { |
| __ ldr(vs1_3, __ Q, __ post(a, 16)); |
| } |
| // load 80 or 88 values from b into vs2_1/2/3 |
| vs_ldpq_post(vs2_1, b); |
| vs_ldpq_post(vs2_2, b); |
| if (i < 2) { |
| __ ldr(vs2_3, __ Q, __ post(b, 16)); |
| } |
| // sum 80 or 88 values across vs1 and vs2 into vs1 |
| vs_addv(vs1_1, __ T8H, vs1_1, vs2_1); |
| vs_addv(vs1_2, __ T8H, vs1_2, vs2_2); |
| if (i < 2) { |
| __ addv(vs1_3, __ T8H, vs1_3, vs2_3); |
| } |
| // load 80 or 88 values from c into vs2_1/2/3 |
| vs_ldpq_post(vs2_1, c); |
| vs_ldpq_post(vs2_2, c); |
| if (i < 2) { |
| __ ldr(vs2_3, __ Q, __ post(c, 16)); |
| } |
| // sum 80 or 88 values across vs1 and vs2 into vs1 |
| vs_addv(vs1_1, __ T8H, vs1_1, vs2_1); |
| vs_addv(vs1_2, __ T8H, vs1_2, vs2_2); |
| if (i < 2) { |
| __ addv(vs1_3, __ T8H, vs1_3, vs2_3); |
| } |
| // add constant to all 80 or 88 results |
| vs_addv(vs1_1, __ T8H, vs1_1, vc_1); |
| vs_addv(vs1_2, __ T8H, vs1_2, vc_2); |
| if (i < 2) { |
| __ addv(vs1_3, __ T8H, vs1_3, vc_3); |
| } |
| // store 80 or 88 values |
| vs_stpq_post(vs1_1, result); |
| vs_stpq_post(vs1_2, result); |
| if (i < 2) { |
| __ str(vs1_3, __ Q, __ post(result, 16)); |
| } |
| } |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Kyber parse XOF output to polynomial coefficient candidates |
| // or decodePoly(12, ...). |
| // Implements |
| // static int implKyber12To16( |
| // byte[] condensed, int index, short[] parsed, int parsedLength) {} |
| // |
| // (parsedLength or (parsedLength - 48) must be divisible by 64.) |
| // |
| // condensed (byte[]) = c_rarg0 |
| // condensedIndex = c_rarg1 |
| // parsed (short[112 or 256]) = c_rarg2 |
| // parsedLength (112 or 256) = c_rarg3 |
| address generate_kyber12To16() { |
| Label L_F00, L_loop, L_end; |
| |
| __ BIND(L_F00); |
| __ emit_int64(0x0f000f000f000f00); |
| __ emit_int64(0x0f000f000f000f00); |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyber12To16_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register condensed = c_rarg0; |
| const Register condensedOffs = c_rarg1; |
| const Register parsed = c_rarg2; |
| const Register parsedLength = c_rarg3; |
| |
| const Register tmpAddr = r11; |
| |
| // Data is input 96 bytes at a time i.e. in groups of 6 x 16B |
| // quadwords so we need a 6 vector sequence for the inputs. |
| // Parsing produces 64 shorts, employing two 8 vector |
| // sequences to store and combine the intermediate data. |
| VSeq<6> vin(24); |
| VSeq<8> va(0), vb(16); |
| |
| __ adr(tmpAddr, L_F00); |
| __ ldr(v31, __ Q, tmpAddr); // 8H times 0x0f00 |
| __ add(condensed, condensed, condensedOffs); |
| |
| __ BIND(L_loop); |
| // load 96 (6 x 16B) byte values |
| vs_ld3_post(vin, __ T16B, condensed); |
| |
| // The front half of sequence vin (vin[0], vin[1] and vin[2]) |
| // holds 48 (16x3) contiguous bytes from memory striped |
| // horizontally across each of the 16 byte lanes. Equivalently, |
| // that is 16 pairs of 12-bit integers. Likewise the back half |
| // holds the next 48 bytes in the same arrangement. |
| |
| // Each vector in the front half can also be viewed as a vertical |
| // strip across the 16 pairs of 12 bit integers. Each byte in |
| // vin[0] stores the low 8 bits of the first int in a pair. Each |
| // byte in vin[1] stores the high 4 bits of the first int and the |
| // low 4 bits of the second int. Each byte in vin[2] stores the |
| // high 8 bits of the second int. Likewise the vectors in second |
| // half. |
| |
| // Converting the data to 16-bit shorts requires first of all |
| // expanding each of the 6 x 16B vectors into 6 corresponding |
| // pairs of 8H vectors. Mask, shift and add operations on the |
| // resulting vector pairs can be used to combine 4 and 8 bit |
| // parts of related 8H vector elements. |
| // |
| // The middle vectors (vin[2] and vin[5]) are actually expanded |
| // twice, one copy manipulated to provide the lower 4 bits |
| // belonging to the first short in a pair and another copy |
| // manipulated to provide the higher 4 bits belonging to the |
| // second short in a pair. This is why the the vector sequences va |
| // and vb used to hold the expanded 8H elements are of length 8. |
| |
| // Expand vin[0] into va[0:1], and vin[1] into va[2:3] and va[4:5] |
| // n.b. target elements 2 and 3 duplicate elements 4 and 5 |
| __ ushll(va[0], __ T8H, vin[0], __ T8B, 0); |
| __ ushll2(va[1], __ T8H, vin[0], __ T16B, 0); |
| __ ushll(va[2], __ T8H, vin[1], __ T8B, 0); |
| __ ushll2(va[3], __ T8H, vin[1], __ T16B, 0); |
| __ ushll(va[4], __ T8H, vin[1], __ T8B, 0); |
| __ ushll2(va[5], __ T8H, vin[1], __ T16B, 0); |
| |
| // likewise expand vin[3] into vb[0:1], and vin[4] into vb[2:3] |
| // and vb[4:5] |
| __ ushll(vb[0], __ T8H, vin[3], __ T8B, 0); |
| __ ushll2(vb[1], __ T8H, vin[3], __ T16B, 0); |
| __ ushll(vb[2], __ T8H, vin[4], __ T8B, 0); |
| __ ushll2(vb[3], __ T8H, vin[4], __ T16B, 0); |
| __ ushll(vb[4], __ T8H, vin[4], __ T8B, 0); |
| __ ushll2(vb[5], __ T8H, vin[4], __ T16B, 0); |
| |
| // shift lo byte of copy 1 of the middle stripe into the high byte |
| __ shl(va[2], __ T8H, va[2], 8); |
| __ shl(va[3], __ T8H, va[3], 8); |
| __ shl(vb[2], __ T8H, vb[2], 8); |
| __ shl(vb[3], __ T8H, vb[3], 8); |
| |
| // expand vin[2] into va[6:7] and vin[5] into vb[6:7] but this |
| // time pre-shifted by 4 to ensure top bits of input 12-bit int |
| // are in bit positions [4..11]. |
| __ ushll(va[6], __ T8H, vin[2], __ T8B, 4); |
| __ ushll2(va[7], __ T8H, vin[2], __ T16B, 4); |
| __ ushll(vb[6], __ T8H, vin[5], __ T8B, 4); |
| __ ushll2(vb[7], __ T8H, vin[5], __ T16B, 4); |
| |
| // mask hi 4 bits of the 1st 12-bit int in a pair from copy1 and |
| // shift lo 4 bits of the 2nd 12-bit int in a pair to the bottom of |
| // copy2 |
| __ andr(va[2], __ T16B, va[2], v31); |
| __ andr(va[3], __ T16B, va[3], v31); |
| __ ushr(va[4], __ T8H, va[4], 4); |
| __ ushr(va[5], __ T8H, va[5], 4); |
| __ andr(vb[2], __ T16B, vb[2], v31); |
| __ andr(vb[3], __ T16B, vb[3], v31); |
| __ ushr(vb[4], __ T8H, vb[4], 4); |
| __ ushr(vb[5], __ T8H, vb[5], 4); |
| |
| // sum hi 4 bits and lo 8 bits of the 1st 12-bit int in each pair and |
| // hi 8 bits plus lo 4 bits of the 2nd 12-bit int in each pair |
| // n.b. the ordering ensures: i) inputs are consumed before they |
| // are overwritten ii) the order of 16-bit results across successive |
| // pairs of vectors in va and then vb reflects the order of the |
| // corresponding 12-bit inputs |
| __ addv(va[0], __ T8H, va[0], va[2]); |
| __ addv(va[2], __ T8H, va[1], va[3]); |
| __ addv(va[1], __ T8H, va[4], va[6]); |
| __ addv(va[3], __ T8H, va[5], va[7]); |
| __ addv(vb[0], __ T8H, vb[0], vb[2]); |
| __ addv(vb[2], __ T8H, vb[1], vb[3]); |
| __ addv(vb[1], __ T8H, vb[4], vb[6]); |
| __ addv(vb[3], __ T8H, vb[5], vb[7]); |
| |
| // store 64 results interleaved as shorts |
| vs_st2_post(vs_front(va), __ T8H, parsed); |
| vs_st2_post(vs_front(vb), __ T8H, parsed); |
| |
| __ sub(parsedLength, parsedLength, 64); |
| __ cmp(parsedLength, (u1)64); |
| __ br(Assembler::GE, L_loop); |
| __ cbz(parsedLength, L_end); |
| |
| // if anything is left it should be a final 72 bytes of input |
| // i.e. a final 48 12-bit values. so we handle this by loading |
| // 48 bytes into all 16B lanes of front(vin) and only 24 |
| // bytes into the lower 8B lane of back(vin) |
| vs_ld3_post(vs_front(vin), __ T16B, condensed); |
| vs_ld3(vs_back(vin), __ T8B, condensed); |
| |
| // Expand vin[0] into va[0:1], and vin[1] into va[2:3] and va[4:5] |
| // n.b. target elements 2 and 3 of va duplicate elements 4 and |
| // 5 and target element 2 of vb duplicates element 4. |
| __ ushll(va[0], __ T8H, vin[0], __ T8B, 0); |
| __ ushll2(va[1], __ T8H, vin[0], __ T16B, 0); |
| __ ushll(va[2], __ T8H, vin[1], __ T8B, 0); |
| __ ushll2(va[3], __ T8H, vin[1], __ T16B, 0); |
| __ ushll(va[4], __ T8H, vin[1], __ T8B, 0); |
| __ ushll2(va[5], __ T8H, vin[1], __ T16B, 0); |
| |
| // This time expand just the lower 8 lanes |
| __ ushll(vb[0], __ T8H, vin[3], __ T8B, 0); |
| __ ushll(vb[2], __ T8H, vin[4], __ T8B, 0); |
| __ ushll(vb[4], __ T8H, vin[4], __ T8B, 0); |
| |
| // shift lo byte of copy 1 of the middle stripe into the high byte |
| __ shl(va[2], __ T8H, va[2], 8); |
| __ shl(va[3], __ T8H, va[3], 8); |
| __ shl(vb[2], __ T8H, vb[2], 8); |
| |
| // expand vin[2] into va[6:7] and lower 8 lanes of vin[5] into |
| // vb[6] pre-shifted by 4 to ensure top bits of the input 12-bit |
| // int are in bit positions [4..11]. |
| __ ushll(va[6], __ T8H, vin[2], __ T8B, 4); |
| __ ushll2(va[7], __ T8H, vin[2], __ T16B, 4); |
| __ ushll(vb[6], __ T8H, vin[5], __ T8B, 4); |
| |
| // mask hi 4 bits of each 1st 12-bit int in pair from copy1 and |
| // shift lo 4 bits of each 2nd 12-bit int in pair to bottom of |
| // copy2 |
| __ andr(va[2], __ T16B, va[2], v31); |
| __ andr(va[3], __ T16B, va[3], v31); |
| __ ushr(va[4], __ T8H, va[4], 4); |
| __ ushr(va[5], __ T8H, va[5], 4); |
| __ andr(vb[2], __ T16B, vb[2], v31); |
| __ ushr(vb[4], __ T8H, vb[4], 4); |
| |
| |
| |
| // sum hi 4 bits and lo 8 bits of each 1st 12-bit int in pair and |
| // hi 8 bits plus lo 4 bits of each 2nd 12-bit int in pair |
| |
| // n.b. ordering ensures: i) inputs are consumed before they are |
| // overwritten ii) order of 16-bit results across succsessive |
| // pairs of vectors in va and then lower half of vb reflects order |
| // of corresponding 12-bit inputs |
| __ addv(va[0], __ T8H, va[0], va[2]); |
| __ addv(va[2], __ T8H, va[1], va[3]); |
| __ addv(va[1], __ T8H, va[4], va[6]); |
| __ addv(va[3], __ T8H, va[5], va[7]); |
| __ addv(vb[0], __ T8H, vb[0], vb[2]); |
| __ addv(vb[1], __ T8H, vb[4], vb[6]); |
| |
| // store 48 results interleaved as shorts |
| vs_st2_post(vs_front(va), __ T8H, parsed); |
| vs_st2_post(vs_front(vs_front(vb)), __ T8H, parsed); |
| |
| __ BIND(L_end); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Kyber Barrett reduce function. |
| // Implements |
| // static int implKyberBarrettReduce(short[] coeffs) {} |
| // |
| // coeffs (short[256]) = c_rarg0 |
| address generate_kyberBarrettReduce() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::kyberBarrettReduce_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register coeffs = c_rarg0; |
| |
| const Register kyberConsts = r10; |
| const Register result = r11; |
| |
| // As above we process 256 sets of values in total i.e. 32 x |
| // 8H quadwords. So, we can load, add and store the data in 3 |
| // groups of 11, 11 and 10 at a time i.e. we need to map sets |
| // of 10 or 11 registers. A further constraint is that the |
| // mapping needs to skip callee saves. So, we allocate the |
| // register sequences using two 8 sequences, two 2 sequences |
| // and two single registers. |
| VSeq<8> vs1_1(0); |
| VSeq<2> vs1_2(16); |
| FloatRegister vs1_3 = v28; |
| VSeq<8> vs2_1(18); |
| VSeq<2> vs2_2(26); |
| FloatRegister vs2_3 = v29; |
| |
| // we also need a pair of corresponding constant sequences |
| |
| VSeq<8> vc1_1(30, 0); |
| VSeq<2> vc1_2(30, 0); |
| FloatRegister vc1_3 = v30; // for kyber_q |
| |
| VSeq<8> vc2_1(31, 0); |
| VSeq<2> vc2_2(31, 0); |
| FloatRegister vc2_3 = v31; // for kyberBarrettMultiplier |
| |
| __ add(result, coeffs, 0); |
| __ lea(kyberConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_kyberConsts)); |
| |
| // load q and the multiplier for the Barrett reduction |
| __ add(kyberConsts, kyberConsts, 16); |
| __ ldpq(vc1_3, vc2_3, kyberConsts); |
| |
| for (int i = 0; i < 3; i++) { |
| // load 80 or 88 coefficients |
| vs_ldpq_post(vs1_1, coeffs); |
| vs_ldpq_post(vs1_2, coeffs); |
| if (i < 2) { |
| __ ldr(vs1_3, __ Q, __ post(coeffs, 16)); |
| } |
| |
| // vs2 <- (2 * vs1 * kyberBarrettMultiplier) >> 16 |
| vs_sqdmulh(vs2_1, __ T8H, vs1_1, vc2_1); |
| vs_sqdmulh(vs2_2, __ T8H, vs1_2, vc2_2); |
| if (i < 2) { |
| __ sqdmulh(vs2_3, __ T8H, vs1_3, vc2_3); |
| } |
| |
| // vs2 <- (vs1 * kyberBarrettMultiplier) >> 26 |
| vs_sshr(vs2_1, __ T8H, vs2_1, 11); |
| vs_sshr(vs2_2, __ T8H, vs2_2, 11); |
| if (i < 2) { |
| __ sshr(vs2_3, __ T8H, vs2_3, 11); |
| } |
| |
| // vs1 <- vs1 - vs2 * kyber_q |
| vs_mlsv(vs1_1, __ T8H, vs2_1, vc1_1); |
| vs_mlsv(vs1_2, __ T8H, vs2_2, vc1_2); |
| if (i < 2) { |
| __ mlsv(vs1_3, __ T8H, vs2_3, vc1_3); |
| } |
| |
| vs_stpq_post(vs1_1, result); |
| vs_stpq_post(vs1_2, result); |
| if (i < 2) { |
| __ str(vs1_3, __ Q, __ post(result, 16)); |
| } |
| } |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| |
| // Dilithium-specific montmul helper routines that generate parallel |
| // code for, respectively, a single 4x4s vector sequence montmul or |
| // two such multiplies in a row. |
| |
| // Perform 16 32-bit Montgomery multiplications in parallel |
| void dilithium_montmul16(const VSeq<4>& va, const VSeq<4>& vb, const VSeq<4>& vc, |
| const VSeq<4>& vtmp, const VSeq<2>& vq) { |
| // Use the helper routine to schedule a 4x4S Montgomery multiply. |
| // It will assert that the register use is valid |
| vs_montmul4(va, vb, vc, __ T4S, vtmp, vq); |
| } |
| |
| // Perform 2x16 32-bit Montgomery multiplications in parallel |
| void dilithium_montmul32(const VSeq<8>& va, const VSeq<8>& vb, const VSeq<8>& vc, |
| const VSeq<4>& vtmp, const VSeq<2>& vq) { |
| // Schedule two successive 4x4S multiplies via the montmul helper |
| // on the front and back halves of va, vb and vc. The helper will |
| // assert that the register use has no overlap conflicts on each |
| // individual call but we also need to ensure that the necessary |
| // disjoint/equality constraints are met across both calls. |
| |
| // vb, vc, vtmp and vq must be disjoint. va must either be |
| // disjoint from all other registers or equal vc |
| |
| assert(vs_disjoint(vb, vc), "vb and vc overlap"); |
| assert(vs_disjoint(vb, vq), "vb and vq overlap"); |
| assert(vs_disjoint(vb, vtmp), "vb and vtmp overlap"); |
| |
| assert(vs_disjoint(vc, vq), "vc and vq overlap"); |
| assert(vs_disjoint(vc, vtmp), "vc and vtmp overlap"); |
| |
| assert(vs_disjoint(vq, vtmp), "vq and vtmp overlap"); |
| |
| assert(vs_disjoint(va, vc) || vs_same(va, vc), "va and vc neither disjoint nor equal"); |
| assert(vs_disjoint(va, vb), "va and vb overlap"); |
| assert(vs_disjoint(va, vq), "va and vq overlap"); |
| assert(vs_disjoint(va, vtmp), "va and vtmp overlap"); |
| |
| // We multiply the front and back halves of each sequence 4 at a |
| // time because |
| // |
| // 1) we are currently only able to get 4-way instruction |
| // parallelism at best |
| // |
| // 2) we need registers for the constants in vq and temporary |
| // scratch registers to hold intermediate results so vtmp can only |
| // be a VSeq<4> which means we only have 4 scratch slots. |
| |
| vs_montmul4(vs_front(va), vs_front(vb), vs_front(vc), __ T4S, vtmp, vq); |
| vs_montmul4(vs_back(va), vs_back(vb), vs_back(vc), __ T4S, vtmp, vq); |
| } |
| |
| // Perform combined montmul then add/sub on 4x4S vectors. |
| void dilithium_montmul16_sub_add( |
| const VSeq<4>& va0, const VSeq<4>& va1, const VSeq<4>& vc, |
| const VSeq<4>& vtmp, const VSeq<2>& vq) { |
| // compute a = montmul(a1, c) |
| dilithium_montmul16(vc, va1, vc, vtmp, vq); |
| // ouptut a1 = a0 - a |
| vs_subv(va1, __ T4S, va0, vc); |
| // and a0 = a0 + a |
| vs_addv(va0, __ T4S, va0, vc); |
| } |
| |
| // Perform combined add/sub then montul on 4x4S vectors. |
| void dilithium_sub_add_montmul16( |
| const VSeq<4>& va0, const VSeq<4>& va1, const VSeq<4>& vb, |
| const VSeq<4>& vtmp1, const VSeq<4>& vtmp2, const VSeq<2>& vq) { |
| // compute c = a0 - a1 |
| vs_subv(vtmp1, __ T4S, va0, va1); |
| // output a0 = a0 + a1 |
| vs_addv(va0, __ T4S, va0, va1); |
| // output a1 = b montmul c |
| dilithium_montmul16(va1, vtmp1, vb, vtmp2, vq); |
| } |
| |
| // At these levels, the indices that correspond to the 'j's (and 'j+l's) |
| // in the Java implementation come in sequences of at least 8, so we |
| // can use ldpq to collect the corresponding data into pairs of vector |
| // registers. |
| // We collect the coefficients corresponding to the 'j+l' indexes into |
| // the vector registers v0-v7, the zetas into the vector registers v16-v23 |
| // then we do the (Montgomery) multiplications by the zetas in parallel |
| // into v16-v23, load the coeffs corresponding to the 'j' indexes into |
| // v0-v7, then do the additions into v24-v31 and the subtractions into |
| // v0-v7 and finally save the results back to the coeffs array. |
| void dilithiumNttLevel0_4(const Register dilithiumConsts, |
| const Register coeffs, const Register zetas) { |
| int c1 = 0; |
| int c2 = 512; |
| int startIncr; |
| // don't use callee save registers v8 - v15 |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x4s inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| int offsets[4] = { 0, 32, 64, 96 }; |
| |
| for (int level = 0; level < 5; level++) { |
| int c1Start = c1; |
| int c2Start = c2; |
| if (level == 3) { |
| offsets[1] = 32; |
| offsets[2] = 128; |
| offsets[3] = 160; |
| } else if (level == 4) { |
| offsets[1] = 64; |
| offsets[2] = 128; |
| offsets[3] = 192; |
| } |
| |
| // For levels 1 - 4 we simply load 2 x 4 adjacent values at a |
| // time at 4 different offsets and multiply them in order by the |
| // next set of input values. So we employ indexed load and store |
| // pair instructions with arrangement 4S. |
| for (int i = 0; i < 4; i++) { |
| // reload q and qinv |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // load 8x4S coefficients via second start pos == c2 |
| vs_ldpq_indexed(vs1, coeffs, c2Start, offsets); |
| // load next 8x4S inputs == b |
| vs_ldpq_post(vs2, zetas); |
| // compute a == c2 * b mod MONT_Q |
| dilithium_montmul32(vs2, vs1, vs2, vtmp, vq); |
| // load 8x4s coefficients via first start pos == c1 |
| vs_ldpq_indexed(vs1, coeffs, c1Start, offsets); |
| // compute a1 = c1 + a |
| vs_addv(vs3, __ T4S, vs1, vs2); |
| // compute a2 = c1 - a |
| vs_subv(vs1, __ T4S, vs1, vs2); |
| // output a1 and a2 |
| vs_stpq_indexed(vs3, coeffs, c1Start, offsets); |
| vs_stpq_indexed(vs1, coeffs, c2Start, offsets); |
| |
| int k = 4 * level + i; |
| |
| if (k > 7) { |
| startIncr = 256; |
| } else if (k == 5) { |
| startIncr = 384; |
| } else { |
| startIncr = 128; |
| } |
| |
| c1Start += startIncr; |
| c2Start += startIncr; |
| } |
| |
| c2 /= 2; |
| } |
| } |
| |
| // Dilithium NTT function except for the final "normalization" to |coeff| < Q. |
| // Implements the method |
| // static int implDilithiumAlmostNtt(int[] coeffs, int zetas[]) {} |
| // of the Java class sun.security.provider |
| // |
| // coeffs (int[256]) = c_rarg0 |
| // zetas (int[256]) = c_rarg1 |
| address generate_dilithiumAlmostNtt() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::dilithiumAlmostNtt_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register coeffs = c_rarg0; |
| const Register zetas = c_rarg1; |
| |
| const Register tmpAddr = r9; |
| const Register dilithiumConsts = r10; |
| const Register result = r11; |
| // don't use callee save registers v8 - v15 |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x4s inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| int offsets[4] = { 0, 32, 64, 96}; |
| int offsets1[8] = { 16, 48, 80, 112, 144, 176, 208, 240 }; |
| int offsets2[8] = { 0, 32, 64, 96, 128, 160, 192, 224 }; |
| __ add(result, coeffs, 0); |
| __ lea(dilithiumConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_dilithiumConsts)); |
| |
| // Each level represents one iteration of the outer for loop of the Java version. |
| |
| // level 0-4 |
| dilithiumNttLevel0_4(dilithiumConsts, coeffs, zetas); |
| |
| // level 5 |
| |
| // At level 5 the coefficients we need to combine with the zetas |
| // are grouped in memory in blocks of size 4. So, for both sets of |
| // coefficients we load 4 adjacent values at 8 different offsets |
| // using an indexed ldr with register variant Q and multiply them |
| // in sequence order by the next set of inputs. Likewise we store |
| // the resuls using an indexed str with register variant Q. |
| for (int i = 0; i < 1024; i += 256) { |
| // reload constants q, qinv each iteration as they get clobbered later |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // load 32 (8x4S) coefficients via first offsets = c1 |
| vs_ldr_indexed(vs1, __ Q, coeffs, i, offsets1); |
| // load next 32 (8x4S) inputs = b |
| vs_ldpq_post(vs2, zetas); |
| // a = b montul c1 |
| dilithium_montmul32(vs2, vs1, vs2, vtmp, vq); |
| // load 32 (8x4S) coefficients via second offsets = c2 |
| vs_ldr_indexed(vs1, __ Q, coeffs, i, offsets2); |
| // add/sub with result of multiply |
| vs_addv(vs3, __ T4S, vs1, vs2); // a1 = a - c2 |
| vs_subv(vs1, __ T4S, vs1, vs2); // a0 = a + c1 |
| // write back new coefficients using same offsets |
| vs_str_indexed(vs3, __ Q, coeffs, i, offsets2); |
| vs_str_indexed(vs1, __ Q, coeffs, i, offsets1); |
| } |
| |
| // level 6 |
| // At level 6 the coefficients we need to combine with the zetas |
| // are grouped in memory in pairs, the first two being montmul |
| // inputs and the second add/sub inputs. We can still implement |
| // the montmul+sub+add using 4-way parallelism but only if we |
| // combine the coefficients with the zetas 16 at a time. We load 8 |
| // adjacent values at 4 different offsets using an ld2 load with |
| // arrangement 2D. That interleaves the lower and upper halves of |
| // each pair of quadwords into successive vector registers. We |
| // then need to montmul the 4 even elements of the coefficients |
| // register sequence by the zetas in order and then add/sub the 4 |
| // odd elements of the coefficients register sequence. We use an |
| // equivalent st2 operation to store the results back into memory |
| // de-interleaved. |
| for (int i = 0; i < 1024; i += 128) { |
| // reload constants q, qinv each iteration as they get clobbered later |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // load interleaved 16 (4x2D) coefficients via offsets |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, i, offsets); |
| // load next 16 (4x4S) inputs |
| vs_ldpq_post(vs_front(vs2), zetas); |
| // mont multiply odd elements of vs1 by vs2 and add/sub into odds/evens |
| dilithium_montmul16_sub_add(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vtmp, vq); |
| // store interleaved 16 (4x2D) coefficients via offsets |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, i, offsets); |
| } |
| |
| // level 7 |
| // At level 7 the coefficients we need to combine with the zetas |
| // occur singly with montmul inputs alterating with add/sub |
| // inputs. Once again we can use 4-way parallelism to combine 16 |
| // zetas at a time. However, we have to load 8 adjacent values at |
| // 4 different offsets using an ld2 load with arrangement 4S. That |
| // interleaves the the odd words of each pair into one |
| // coefficients vector register and the even words of the pair |
| // into the next register. We then need to montmul the 4 even |
| // elements of the coefficients register sequence by the zetas in |
| // order and then add/sub the 4 odd elements of the coefficients |
| // register sequence. We use an equivalent st2 operation to store |
| // the results back into memory de-interleaved. |
| |
| for (int i = 0; i < 1024; i += 128) { |
| // reload constants q, qinv each iteration as they get clobbered later |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // load interleaved 16 (4x4S) coefficients via offsets |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, i, offsets); |
| // load next 16 (4x4S) inputs |
| vs_ldpq_post(vs_front(vs2), zetas); |
| // mont multiply odd elements of vs1 by vs2 and add/sub into odds/evens |
| dilithium_montmul16_sub_add(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vtmp, vq); |
| // store interleaved 16 (4x4S) coefficients via offsets |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, i, offsets); |
| } |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // At these levels, the indices that correspond to the 'j's (and 'j+l's) |
| // in the Java implementation come in sequences of at least 8, so we |
| // can use ldpq to collect the corresponding data into pairs of vector |
| // registers |
| // We collect the coefficients that correspond to the 'j's into vs1 |
| // the coefficiets that correspond to the 'j+l's into vs2 then |
| // do the additions into vs3 and the subtractions into vs1 then |
| // save the result of the additions, load the zetas into vs2 |
| // do the (Montgomery) multiplications by zeta in parallel into vs2 |
| // finally save the results back to the coeffs array |
| void dilithiumInverseNttLevel3_7(const Register dilithiumConsts, |
| const Register coeffs, const Register zetas) { |
| int c1 = 0; |
| int c2 = 32; |
| int startIncr; |
| int offsets[4]; |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x4s inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| |
| offsets[0] = 0; |
| |
| for (int level = 3; level < 8; level++) { |
| int c1Start = c1; |
| int c2Start = c2; |
| if (level == 3) { |
| offsets[1] = 64; |
| offsets[2] = 128; |
| offsets[3] = 192; |
| } else if (level == 4) { |
| offsets[1] = 32; |
| offsets[2] = 128; |
| offsets[3] = 160; |
| } else { |
| offsets[1] = 32; |
| offsets[2] = 64; |
| offsets[3] = 96; |
| } |
| |
| // For levels 3 - 7 we simply load 2 x 4 adjacent values at a |
| // time at 4 different offsets and multiply them in order by the |
| // next set of input values. So we employ indexed load and store |
| // pair instructions with arrangement 4S. |
| for (int i = 0; i < 4; i++) { |
| // load v1 32 (8x4S) coefficients relative to first start index |
| vs_ldpq_indexed(vs1, coeffs, c1Start, offsets); |
| // load v2 32 (8x4S) coefficients relative to second start index |
| vs_ldpq_indexed(vs2, coeffs, c2Start, offsets); |
| // a0 = v1 + v2 -- n.b. clobbers vqs |
| vs_addv(vs3, __ T4S, vs1, vs2); |
| // a1 = v1 - v2 |
| vs_subv(vs1, __ T4S, vs1, vs2); |
| // save a1 relative to first start index |
| vs_stpq_indexed(vs3, coeffs, c1Start, offsets); |
| // load constants q, qinv each iteration as they get clobbered above |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // load b next 32 (8x4S) inputs |
| vs_ldpq_post(vs2, zetas); |
| // a = a1 montmul b |
| dilithium_montmul32(vs2, vs1, vs2, vtmp, vq); |
| // save a relative to second start index |
| vs_stpq_indexed(vs2, coeffs, c2Start, offsets); |
| |
| int k = 4 * level + i; |
| |
| if (k < 24) { |
| startIncr = 256; |
| } else if (k == 25) { |
| startIncr = 384; |
| } else { |
| startIncr = 128; |
| } |
| |
| c1Start += startIncr; |
| c2Start += startIncr; |
| } |
| |
| c2 *= 2; |
| } |
| } |
| |
| // Dilithium Inverse NTT function except the final mod Q division by 2^256. |
| // Implements the method |
| // static int implDilithiumAlmostInverseNtt(int[] coeffs, int[] zetas) {} of |
| // the sun.security.provider.ML_DSA class. |
| // |
| // coeffs (int[256]) = c_rarg0 |
| // zetas (int[256]) = c_rarg1 |
| address generate_dilithiumAlmostInverseNtt() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::dilithiumAlmostInverseNtt_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| const Register coeffs = c_rarg0; |
| const Register zetas = c_rarg1; |
| |
| const Register tmpAddr = r9; |
| const Register dilithiumConsts = r10; |
| const Register result = r11; |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x4s inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| int offsets[4] = { 0, 32, 64, 96 }; |
| int offsets1[8] = { 0, 32, 64, 96, 128, 160, 192, 224 }; |
| int offsets2[8] = { 16, 48, 80, 112, 144, 176, 208, 240 }; |
| |
| __ add(result, coeffs, 0); |
| __ lea(dilithiumConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_dilithiumConsts)); |
| |
| // Each level represents one iteration of the outer for loop of the Java version |
| |
| // level 0 |
| // At level 0 we need to interleave adjacent quartets of |
| // coefficients before we multiply and add/sub by the next 16 |
| // zetas just as we did for level 7 in the multiply code. So we |
| // load and store the values using an ld2/st2 with arrangement 4S. |
| for (int i = 0; i < 1024; i += 128) { |
| // load constants q, qinv |
| // n.b. this can be moved out of the loop as they do not get |
| // clobbered by first two loops |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // a0/a1 load interleaved 32 (8x4S) coefficients |
| vs_ld2_indexed(vs1, __ T4S, coeffs, tmpAddr, i, offsets); |
| // b load next 32 (8x4S) inputs |
| vs_ldpq_post(vs_front(vs2), zetas); |
| // compute in parallel (a0, a1) = (a0 + a1, (a0 - a1) montmul b) |
| // n.b. second half of vs2 provides temporary register storage |
| dilithium_sub_add_montmul16(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| // a0/a1 store interleaved 32 (8x4S) coefficients |
| vs_st2_indexed(vs1, __ T4S, coeffs, tmpAddr, i, offsets); |
| } |
| |
| // level 1 |
| // At level 1 we need to interleave pairs of adjacent pairs of |
| // coefficients before we multiply by the next 16 zetas just as we |
| // did for level 6 in the multiply code. So we load and store the |
| // values an ld2/st2 with arrangement 2D. |
| for (int i = 0; i < 1024; i += 128) { |
| // a0/a1 load interleaved 32 (8x2D) coefficients |
| vs_ld2_indexed(vs1, __ T2D, coeffs, tmpAddr, i, offsets); |
| // b load next 16 (4x4S) inputs |
| vs_ldpq_post(vs_front(vs2), zetas); |
| // compute in parallel (a0, a1) = (a0 + a1, (a0 - a1) montmul b) |
| // n.b. second half of vs2 provides temporary register storage |
| dilithium_sub_add_montmul16(vs_even(vs1), vs_odd(vs1), |
| vs_front(vs2), vs_back(vs2), vtmp, vq); |
| // a0/a1 store interleaved 32 (8x2D) coefficients |
| vs_st2_indexed(vs1, __ T2D, coeffs, tmpAddr, i, offsets); |
| } |
| |
| // level 2 |
| // At level 2 coefficients come in blocks of 4. So, we load 4 |
| // adjacent coefficients at 8 distinct offsets for both the first |
| // and second coefficient sequences, using an ldr with register |
| // variant Q then combine them with next set of 32 zetas. Likewise |
| // we store the results using an str with register variant Q. |
| for (int i = 0; i < 1024; i += 256) { |
| // c0 load 32 (8x4S) coefficients via first offsets |
| vs_ldr_indexed(vs1, __ Q, coeffs, i, offsets1); |
| // c1 load 32 (8x4S) coefficients via second offsets |
| vs_ldr_indexed(vs2, __ Q,coeffs, i, offsets2); |
| // a0 = c0 + c1 n.b. clobbers vq which overlaps vs3 |
| vs_addv(vs3, __ T4S, vs1, vs2); |
| // c = c0 - c1 |
| vs_subv(vs1, __ T4S, vs1, vs2); |
| // store a0 32 (8x4S) coefficients via first offsets |
| vs_str_indexed(vs3, __ Q, coeffs, i, offsets1); |
| // b load 32 (8x4S) next inputs |
| vs_ldpq_post(vs2, zetas); |
| // reload constants q, qinv -- they were clobbered earlier |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // compute a1 = b montmul c |
| dilithium_montmul32(vs2, vs1, vs2, vtmp, vq); |
| // store a1 32 (8x4S) coefficients via second offsets |
| vs_str_indexed(vs2, __ Q, coeffs, i, offsets2); |
| } |
| |
| // level 3-7 |
| dilithiumInverseNttLevel3_7(dilithiumConsts, coeffs, zetas); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Dilithium multiply polynomials in the NTT domain. |
| // Straightforward implementation of the method |
| // static int implDilithiumNttMult( |
| // int[] result, int[] ntta, int[] nttb {} of |
| // the sun.security.provider.ML_DSA class. |
| // |
| // result (int[256]) = c_rarg0 |
| // poly1 (int[256]) = c_rarg1 |
| // poly2 (int[256]) = c_rarg2 |
| address generate_dilithiumNttMult() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::dilithiumNttMult_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| Label L_loop; |
| |
| const Register result = c_rarg0; |
| const Register poly1 = c_rarg1; |
| const Register poly2 = c_rarg2; |
| |
| const Register dilithiumConsts = r10; |
| const Register len = r11; |
| |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x4s inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| VSeq<8> vrsquare(29, 0); // for montmul by constant RSQUARE |
| |
| __ lea(dilithiumConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_dilithiumConsts)); |
| |
| // load constants q, qinv |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // load constant rSquare into v29 |
| __ ldr(v29, __ Q, Address(dilithiumConsts, 48)); // rSquare |
| |
| __ mov(len, zr); |
| __ add(len, len, 1024); |
| |
| __ BIND(L_loop); |
| |
| // b load 32 (8x4S) next inputs from poly1 |
| vs_ldpq_post(vs1, poly1); |
| // c load 32 (8x4S) next inputs from poly2 |
| vs_ldpq_post(vs2, poly2); |
| // compute a = b montmul c |
| dilithium_montmul32(vs2, vs1, vs2, vtmp, vq); |
| // compute a = rsquare montmul a |
| dilithium_montmul32(vs2, vrsquare, vs2, vtmp, vq); |
| // save a 32 (8x4S) results |
| vs_stpq_post(vs2, result); |
| |
| __ sub(len, len, 128); |
| __ cmp(len, (u1)128); |
| __ br(Assembler::GE, L_loop); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Dilithium Motgomery multiply an array by a constant. |
| // A straightforward implementation of the method |
| // static int implDilithiumMontMulByConstant(int[] coeffs, int constant) {} |
| // of the sun.security.provider.MLDSA class |
| // |
| // coeffs (int[256]) = c_rarg0 |
| // constant (int) = c_rarg1 |
| address generate_dilithiumMontMulByConstant() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::dilithiumMontMulByConstant_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ enter(); |
| |
| Label L_loop; |
| |
| const Register coeffs = c_rarg0; |
| const Register constant = c_rarg1; |
| |
| const Register dilithiumConsts = r10; |
| const Register result = r11; |
| const Register len = r12; |
| |
| VSeq<8> vs1(0), vs2(16), vs3(24); // 3 sets of 8x4s inputs/outputs |
| VSeq<4> vtmp = vs_front(vs3); // n.b. tmp registers overlap vs3 |
| VSeq<2> vq(30); // n.b. constants overlap vs3 |
| VSeq<8> vconst(29, 0); // for montmul by constant |
| |
| // results track inputs |
| __ add(result, coeffs, 0); |
| __ lea(dilithiumConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_dilithiumConsts)); |
| |
| // load constants q, qinv -- they do not get clobbered by first two loops |
| vs_ldpq(vq, dilithiumConsts); // qInv, q |
| // copy caller supplied constant across vconst |
| __ dup(vconst[0], __ T4S, constant); |
| __ mov(len, zr); |
| __ add(len, len, 1024); |
| |
| __ BIND(L_loop); |
| |
| // load next 32 inputs |
| vs_ldpq_post(vs2, coeffs); |
| // mont mul by constant |
| dilithium_montmul32(vs2, vconst, vs2, vtmp, vq); |
| // write next 32 results |
| vs_stpq_post(vs2, result); |
| |
| __ sub(len, len, 128); |
| __ cmp(len, (u1)128); |
| __ br(Assembler::GE, L_loop); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Dilithium decompose poly. |
| // Implements the method |
| // static int implDilithiumDecomposePoly(int[] coeffs, int constant) {} |
| // of the sun.security.provider.ML_DSA class |
| // |
| // input (int[256]) = c_rarg0 |
| // lowPart (int[256]) = c_rarg1 |
| // highPart (int[256]) = c_rarg2 |
| // twoGamma2 (int) = c_rarg3 |
| // multiplier (int) = c_rarg4 |
| address generate_dilithiumDecomposePoly() { |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::dilithiumDecomposePoly_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| Label L_loop; |
| |
| const Register input = c_rarg0; |
| const Register lowPart = c_rarg1; |
| const Register highPart = c_rarg2; |
| const Register twoGamma2 = c_rarg3; |
| const Register multiplier = c_rarg4; |
| |
| const Register len = r9; |
| const Register dilithiumConsts = r10; |
| const Register tmp = r11; |
| |
| // 6 independent sets of 4x4s values |
| VSeq<4> vs1(0), vs2(4), vs3(8); |
| VSeq<4> vs4(12), vs5(16), vtmp(20); |
| |
| // 7 constants for cross-multiplying |
| VSeq<4> one(25, 0); |
| VSeq<4> qminus1(26, 0); |
| VSeq<4> g2(27, 0); |
| VSeq<4> twog2(28, 0); |
| VSeq<4> mult(29, 0); |
| VSeq<4> q(30, 0); |
| VSeq<4> qadd(31, 0); |
| |
| __ enter(); |
| |
| __ lea(dilithiumConsts, |
| ExternalAddress((address) StubRoutines::aarch64::_dilithiumConsts)); |
| |
| // save callee-saved registers |
| __ stpd(v8, v9, __ pre(sp, -64)); |
| __ stpd(v10, v11, Address(sp, 16)); |
| __ stpd(v12, v13, Address(sp, 32)); |
| __ stpd(v14, v15, Address(sp, 48)); |
| |
| // populate constant registers |
| __ mov(tmp, zr); |
| __ add(tmp, tmp, 1); |
| __ dup(one[0], __ T4S, tmp); // 1 |
| __ ldr(q[0], __ Q, Address(dilithiumConsts, 16)); // q |
| __ ldr(qadd[0], __ Q, Address(dilithiumConsts, 64)); // addend for mod q reduce |
| __ dup(twog2[0], __ T4S, twoGamma2); // 2 * gamma2 |
| __ dup(mult[0], __ T4S, multiplier); // multiplier for mod 2 * gamma reduce |
| __ subv(qminus1[0], __ T4S, v30, v25); // q - 1 |
| __ sshr(g2[0], __ T4S, v28, 1); // gamma2 |
| |
| __ mov(len, zr); |
| __ add(len, len, 1024); |
| |
| __ BIND(L_loop); |
| |
| // load next 4x4S inputs interleaved: rplus --> vs1 |
| __ ld4(vs1[0], vs1[1], vs1[2], vs1[3], __ T4S, __ post(input, 64)); |
| |
| // rplus = rplus - ((rplus + qadd) >> 23) * q |
| vs_addv(vtmp, __ T4S, vs1, qadd); |
| vs_sshr(vtmp, __ T4S, vtmp, 23); |
| vs_mulv(vtmp, __ T4S, vtmp, q); |
| vs_subv(vs1, __ T4S, vs1, vtmp); |
| |
| // rplus = rplus + ((rplus >> 31) & dilithium_q); |
| vs_sshr(vtmp, __ T4S, vs1, 31); |
| vs_andr(vtmp, vtmp, q); |
| vs_addv(vs1, __ T4S, vs1, vtmp); |
| |
| // quotient --> vs2 |
| // int quotient = (rplus * multiplier) >> 22; |
| vs_mulv(vtmp, __ T4S, vs1, mult); |
| vs_sshr(vs2, __ T4S, vtmp, 22); |
| |
| // r0 --> vs3 |
| // int r0 = rplus - quotient * twoGamma2; |
| vs_mulv(vtmp, __ T4S, vs2, twog2); |
| vs_subv(vs3, __ T4S, vs1, vtmp); |
| |
| // mask --> vs4 |
| // int mask = (twoGamma2 - r0) >> 22; |
| vs_subv(vtmp, __ T4S, twog2, vs3); |
| vs_sshr(vs4, __ T4S, vtmp, 22); |
| |
| // r0 -= (mask & twoGamma2); |
| vs_andr(vtmp, vs4, twog2); |
| vs_subv(vs3, __ T4S, vs3, vtmp); |
| |
| // quotient += (mask & 1); |
| vs_andr(vtmp, vs4, one); |
| vs_addv(vs2, __ T4S, vs2, vtmp); |
| |
| // mask = (twoGamma2 / 2 - r0) >> 31; |
| vs_subv(vtmp, __ T4S, g2, vs3); |
| vs_sshr(vs4, __ T4S, vtmp, 31); |
| |
| // r0 -= (mask & twoGamma2); |
| vs_andr(vtmp, vs4, twog2); |
| vs_subv(vs3, __ T4S, vs3, vtmp); |
| |
| // quotient += (mask & 1); |
| vs_andr(vtmp, vs4, one); |
| vs_addv(vs2, __ T4S, vs2, vtmp); |
| |
| // r1 --> vs5 |
| // int r1 = rplus - r0 - (dilithium_q - 1); |
| vs_subv(vtmp, __ T4S, vs1, vs3); |
| vs_subv(vs5, __ T4S, vtmp, qminus1); |
| |
| // r1 --> vs1 (overwriting rplus) |
| // r1 = (r1 | (-r1)) >> 31; // 0 if rplus - r0 == (dilithium_q - 1), -1 otherwise |
| vs_negr(vtmp, __ T4S, vs5); |
| vs_orr(vtmp, vs5, vtmp); |
| vs_sshr(vs1, __ T4S, vtmp, 31); |
| |
| // r0 += ~r1; |
| vs_notr(vtmp, vs1); |
| vs_addv(vs3, __ T4S, vs3, vtmp); |
| |
| // r1 = r1 & quotient; |
| vs_andr(vs1, vs2, vs1); |
| |
| // store results inteleaved |
| // lowPart[m] = r0; |
| // highPart[m] = r1; |
| __ st4(vs3[0], vs3[1], vs3[2], vs3[3], __ T4S, __ post(lowPart, 64)); |
| __ st4(vs1[0], vs1[1], vs1[2], vs1[3], __ T4S, __ post(highPart, 64)); |
| |
| __ sub(len, len, 64); |
| __ cmp(len, (u1)64); |
| __ br(Assembler::GE, L_loop); |
| |
| // restore callee-saved vector registers |
| __ ldpd(v14, v15, Address(sp, 48)); |
| __ ldpd(v12, v13, Address(sp, 32)); |
| __ ldpd(v10, v11, Address(sp, 16)); |
| __ ldpd(v8, v9, __ post(sp, 64)); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ mov(r0, zr); // return 0 |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| void bcax5(Register a0, Register a1, Register a2, Register a3, Register a4, |
| Register tmp0, Register tmp1, Register tmp2) { |
| __ bic(tmp0, a2, a1); // for a0 |
| __ bic(tmp1, a3, a2); // for a1 |
| __ bic(tmp2, a4, a3); // for a2 |
| __ eor(a2, a2, tmp2); |
| __ bic(tmp2, a0, a4); // for a3 |
| __ eor(a3, a3, tmp2); |
| __ bic(tmp2, a1, a0); // for a4 |
| __ eor(a0, a0, tmp0); |
| __ eor(a1, a1, tmp1); |
| __ eor(a4, a4, tmp2); |
| } |
| |
| void keccak_round_gpr(bool can_use_fp, bool can_use_r18, Register rc, |
| Register a0, Register a1, Register a2, Register a3, Register a4, |
| Register a5, Register a6, Register a7, Register a8, Register a9, |
| Register a10, Register a11, Register a12, Register a13, Register a14, |
| Register a15, Register a16, Register a17, Register a18, Register a19, |
| Register a20, Register a21, Register a22, Register a23, Register a24, |
| Register tmp0, Register tmp1, Register tmp2) { |
| __ eor3(tmp1, a4, a9, a14); |
| __ eor3(tmp0, tmp1, a19, a24); // tmp0 = a4^a9^a14^a19^a24 = c4 |
| __ eor3(tmp2, a1, a6, a11); |
| __ eor3(tmp1, tmp2, a16, a21); // tmp1 = a1^a6^a11^a16^a21 = c1 |
| __ rax1(tmp2, tmp0, tmp1); // d0 |
| { |
| |
| Register tmp3, tmp4; |
| if (can_use_fp && can_use_r18) { |
| tmp3 = rfp; |
| tmp4 = r18_tls; |
| } else { |
| tmp3 = a4; |
| tmp4 = a9; |
| __ stp(tmp3, tmp4, __ pre(sp, -16)); |
| } |
| |
| __ eor3(tmp3, a0, a5, a10); |
| __ eor3(tmp4, tmp3, a15, a20); // tmp4 = a0^a5^a10^a15^a20 = c0 |
| __ eor(a0, a0, tmp2); |
| __ eor(a5, a5, tmp2); |
| __ eor(a10, a10, tmp2); |
| __ eor(a15, a15, tmp2); |
| __ eor(a20, a20, tmp2); // d0(tmp2) |
| __ eor3(tmp3, a2, a7, a12); |
| __ eor3(tmp2, tmp3, a17, a22); // tmp2 = a2^a7^a12^a17^a22 = c2 |
| __ rax1(tmp3, tmp4, tmp2); // d1 |
| __ eor(a1, a1, tmp3); |
| __ eor(a6, a6, tmp3); |
| __ eor(a11, a11, tmp3); |
| __ eor(a16, a16, tmp3); |
| __ eor(a21, a21, tmp3); // d1(tmp3) |
| __ rax1(tmp3, tmp2, tmp0); // d3 |
| __ eor3(tmp2, a3, a8, a13); |
| __ eor3(tmp0, tmp2, a18, a23); // tmp0 = a3^a8^a13^a18^a23 = c3 |
| __ eor(a3, a3, tmp3); |
| __ eor(a8, a8, tmp3); |
| __ eor(a13, a13, tmp3); |
| __ eor(a18, a18, tmp3); |
| __ eor(a23, a23, tmp3); |
| __ rax1(tmp2, tmp1, tmp0); // d2 |
| __ eor(a2, a2, tmp2); |
| __ eor(a7, a7, tmp2); |
| __ eor(a12, a12, tmp2); |
| __ rax1(tmp0, tmp0, tmp4); // d4 |
| if (!can_use_fp || !can_use_r18) { |
| __ ldp(tmp3, tmp4, __ post(sp, 16)); |
| } |
| __ eor(a17, a17, tmp2); |
| __ eor(a22, a22, tmp2); |
| __ eor(a4, a4, tmp0); |
| __ eor(a9, a9, tmp0); |
| __ eor(a14, a14, tmp0); |
| __ eor(a19, a19, tmp0); |
| __ eor(a24, a24, tmp0); |
| } |
| |
| __ rol(tmp0, a10, 3); |
| __ rol(a10, a1, 1); |
| __ rol(a1, a6, 44); |
| __ rol(a6, a9, 20); |
| __ rol(a9, a22, 61); |
| __ rol(a22, a14, 39); |
| __ rol(a14, a20, 18); |
| __ rol(a20, a2, 62); |
| __ rol(a2, a12, 43); |
| __ rol(a12, a13, 25); |
| __ rol(a13, a19, 8) ; |
| __ rol(a19, a23, 56); |
| __ rol(a23, a15, 41); |
| __ rol(a15, a4, 27); |
| __ rol(a4, a24, 14); |
| __ rol(a24, a21, 2); |
| __ rol(a21, a8, 55); |
| __ rol(a8, a16, 45); |
| __ rol(a16, a5, 36); |
| __ rol(a5, a3, 28); |
| __ rol(a3, a18, 21); |
| __ rol(a18, a17, 15); |
| __ rol(a17, a11, 10); |
| __ rol(a11, a7, 6); |
| __ mov(a7, tmp0); |
| |
| bcax5(a0, a1, a2, a3, a4, tmp0, tmp1, tmp2); |
| bcax5(a5, a6, a7, a8, a9, tmp0, tmp1, tmp2); |
| bcax5(a10, a11, a12, a13, a14, tmp0, tmp1, tmp2); |
| bcax5(a15, a16, a17, a18, a19, tmp0, tmp1, tmp2); |
| bcax5(a20, a21, a22, a23, a24, tmp0, tmp1, tmp2); |
| |
| __ ldr(tmp1, __ post(rc, 8)); |
| __ eor(a0, a0, tmp1); |
| |
| } |
| |
| // Arguments: |
| // |
| // Inputs: |
| // c_rarg0 - byte[] source+offset |
| // c_rarg1 - byte[] SHA.state |
| // c_rarg2 - int block_size |
| // c_rarg3 - int offset |
| // c_rarg4 - int limit |
| // |
| address generate_sha3_implCompress_gpr(StubGenStubId stub_id) { |
| bool multi_block; |
| switch (stub_id) { |
| case sha3_implCompress_id: |
| multi_block = false; |
| break; |
| case sha3_implCompressMB_id: |
| multi_block = true; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| static const uint64_t round_consts[24] = { |
| 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, |
| 0x8000000080008000L, 0x000000000000808BL, 0x0000000080000001L, |
| 0x8000000080008081L, 0x8000000000008009L, 0x000000000000008AL, |
| 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL, |
| 0x000000008000808BL, 0x800000000000008BL, 0x8000000000008089L, |
| 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, |
| 0x000000000000800AL, 0x800000008000000AL, 0x8000000080008081L, |
| 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L |
| }; |
| |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register buf = c_rarg0; |
| Register state = c_rarg1; |
| Register block_size = c_rarg2; |
| Register ofs = c_rarg3; |
| Register limit = c_rarg4; |
| |
| // use r3.r17,r19..r28 to keep a0..a24. |
| // a0..a24 are respective locals from SHA3.java |
| Register a0 = r25, |
| a1 = r26, |
| a2 = r27, |
| a3 = r3, |
| a4 = r4, |
| a5 = r5, |
| a6 = r6, |
| a7 = r7, |
| a8 = rscratch1, // r8 |
| a9 = rscratch2, // r9 |
| a10 = r10, |
| a11 = r11, |
| a12 = r12, |
| a13 = r13, |
| a14 = r14, |
| a15 = r15, |
| a16 = r16, |
| a17 = r17, |
| a18 = r28, |
| a19 = r19, |
| a20 = r20, |
| a21 = r21, |
| a22 = r22, |
| a23 = r23, |
| a24 = r24; |
| |
| Register tmp0 = block_size, tmp1 = buf, tmp2 = state, tmp3 = r30; |
| |
| Label sha3_loop, rounds24_preloop, loop_body; |
| Label sha3_512_or_sha3_384, shake128; |
| |
| bool can_use_r18 = false; |
| #ifndef R18_RESERVED |
| can_use_r18 = true; |
| #endif |
| bool can_use_fp = !PreserveFramePointer; |
| |
| __ enter(); |
| |
| // save almost all yet unsaved gpr registers on stack |
| __ str(block_size, __ pre(sp, -128)); |
| if (multi_block) { |
| __ stpw(ofs, limit, Address(sp, 8)); |
| } |
| // 8 bytes at sp+16 will be used to keep buf |
| __ stp(r19, r20, Address(sp, 32)); |
| __ stp(r21, r22, Address(sp, 48)); |
| __ stp(r23, r24, Address(sp, 64)); |
| __ stp(r25, r26, Address(sp, 80)); |
| __ stp(r27, r28, Address(sp, 96)); |
| if (can_use_r18 && can_use_fp) { |
| __ stp(r18_tls, state, Address(sp, 112)); |
| } else { |
| __ str(state, Address(sp, 112)); |
| } |
| |
| // begin sha3 calculations: loading a0..a24 from state arrary |
| __ ldp(a0, a1, state); |
| __ ldp(a2, a3, Address(state, 16)); |
| __ ldp(a4, a5, Address(state, 32)); |
| __ ldp(a6, a7, Address(state, 48)); |
| __ ldp(a8, a9, Address(state, 64)); |
| __ ldp(a10, a11, Address(state, 80)); |
| __ ldp(a12, a13, Address(state, 96)); |
| __ ldp(a14, a15, Address(state, 112)); |
| __ ldp(a16, a17, Address(state, 128)); |
| __ ldp(a18, a19, Address(state, 144)); |
| __ ldp(a20, a21, Address(state, 160)); |
| __ ldp(a22, a23, Address(state, 176)); |
| __ ldr(a24, Address(state, 192)); |
| |
| __ BIND(sha3_loop); |
| |
| // load input |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a0, a0, tmp3); |
| __ eor(a1, a1, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a2, a2, tmp3); |
| __ eor(a3, a3, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a4, a4, tmp3); |
| __ eor(a5, a5, tmp2); |
| __ ldr(tmp3, __ post(buf, 8)); |
| __ eor(a6, a6, tmp3); |
| |
| // block_size == 72, SHA3-512; block_size == 104, SHA3-384 |
| __ tbz(block_size, 7, sha3_512_or_sha3_384); |
| |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a7, a7, tmp3); |
| __ eor(a8, a8, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a9, a9, tmp3); |
| __ eor(a10, a10, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a11, a11, tmp3); |
| __ eor(a12, a12, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a13, a13, tmp3); |
| __ eor(a14, a14, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a15, a15, tmp3); |
| __ eor(a16, a16, tmp2); |
| |
| // block_size == 136, bit4 == 0 and bit5 == 0, SHA3-256 or SHAKE256 |
| __ andw(tmp2, block_size, 48); |
| __ cbzw(tmp2, rounds24_preloop); |
| __ tbnz(block_size, 5, shake128); |
| // block_size == 144, bit5 == 0, SHA3-244 |
| __ ldr(tmp3, __ post(buf, 8)); |
| __ eor(a17, a17, tmp3); |
| __ b(rounds24_preloop); |
| |
| __ BIND(shake128); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a17, a17, tmp3); |
| __ eor(a18, a18, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a19, a19, tmp3); |
| __ eor(a20, a20, tmp2); |
| __ b(rounds24_preloop); // block_size == 168, SHAKE128 |
| |
| __ BIND(sha3_512_or_sha3_384); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a7, a7, tmp3); |
| __ eor(a8, a8, tmp2); |
| __ tbz(block_size, 5, rounds24_preloop); // SHA3-512 |
| |
| // SHA3-384 |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a9, a9, tmp3); |
| __ eor(a10, a10, tmp2); |
| __ ldp(tmp3, tmp2, __ post(buf, 16)); |
| __ eor(a11, a11, tmp3); |
| __ eor(a12, a12, tmp2); |
| |
| __ BIND(rounds24_preloop); |
| __ fmovs(v0, 24.0); // float loop counter, |
| __ fmovs(v1, 1.0); // exact representation |
| |
| __ str(buf, Address(sp, 16)); |
| __ lea(tmp3, ExternalAddress((address) round_consts)); |
| |
| __ BIND(loop_body); |
| keccak_round_gpr(can_use_fp, can_use_r18, tmp3, |
| a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, |
| a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, |
| tmp0, tmp1, tmp2); |
| __ fsubs(v0, v0, v1); |
| __ fcmps(v0, 0.0); |
| __ br(__ NE, loop_body); |
| |
| if (multi_block) { |
| __ ldrw(block_size, sp); // block_size |
| __ ldpw(tmp2, tmp1, Address(sp, 8)); // offset, limit |
| __ addw(tmp2, tmp2, block_size); |
| __ cmpw(tmp2, tmp1); |
| __ strw(tmp2, Address(sp, 8)); // store offset in case we're jumping |
| __ ldr(buf, Address(sp, 16)); // restore buf in case we're jumping |
| __ br(Assembler::LE, sha3_loop); |
| __ movw(c_rarg0, tmp2); // return offset |
| } |
| if (can_use_fp && can_use_r18) { |
| __ ldp(r18_tls, state, Address(sp, 112)); |
| } else { |
| __ ldr(state, Address(sp, 112)); |
| } |
| // save calculated sha3 state |
| __ stp(a0, a1, Address(state)); |
| __ stp(a2, a3, Address(state, 16)); |
| __ stp(a4, a5, Address(state, 32)); |
| __ stp(a6, a7, Address(state, 48)); |
| __ stp(a8, a9, Address(state, 64)); |
| __ stp(a10, a11, Address(state, 80)); |
| __ stp(a12, a13, Address(state, 96)); |
| __ stp(a14, a15, Address(state, 112)); |
| __ stp(a16, a17, Address(state, 128)); |
| __ stp(a18, a19, Address(state, 144)); |
| __ stp(a20, a21, Address(state, 160)); |
| __ stp(a22, a23, Address(state, 176)); |
| __ str(a24, Address(state, 192)); |
| |
| // restore required registers from stack |
| __ ldp(r19, r20, Address(sp, 32)); |
| __ ldp(r21, r22, Address(sp, 48)); |
| __ ldp(r23, r24, Address(sp, 64)); |
| __ ldp(r25, r26, Address(sp, 80)); |
| __ ldp(r27, r28, Address(sp, 96)); |
| if (can_use_fp && can_use_r18) { |
| __ add(rfp, sp, 128); // leave() will copy rfp to sp below |
| } // else no need to recalculate rfp, since it wasn't changed |
| |
| __ leave(); |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| /** |
| * Arguments: |
| * |
| * Inputs: |
| * c_rarg0 - int crc |
| * c_rarg1 - byte* buf |
| * c_rarg2 - int length |
| * |
| * Output: |
| * rax - int crc result |
| */ |
| address generate_updateBytesCRC32() { |
| assert(UseCRC32Intrinsics, "what are we doing here?"); |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::updateBytesCRC32_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| |
| const Register crc = c_rarg0; // crc |
| const Register buf = c_rarg1; // source java byte array address |
| const Register len = c_rarg2; // length |
| const Register table0 = c_rarg3; // crc_table address |
| const Register table1 = c_rarg4; |
| const Register table2 = c_rarg5; |
| const Register table3 = c_rarg6; |
| const Register tmp3 = c_rarg7; |
| |
| BLOCK_COMMENT("Entry:"); |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| __ kernel_crc32(crc, buf, len, |
| table0, table1, table2, table3, rscratch1, rscratch2, tmp3); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| /** |
| * Arguments: |
| * |
| * Inputs: |
| * c_rarg0 - int crc |
| * c_rarg1 - byte* buf |
| * c_rarg2 - int length |
| * c_rarg3 - int* table |
| * |
| * Output: |
| * r0 - int crc result |
| */ |
| address generate_updateBytesCRC32C() { |
| assert(UseCRC32CIntrinsics, "what are we doing here?"); |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::updateBytesCRC32C_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| |
| const Register crc = c_rarg0; // crc |
| const Register buf = c_rarg1; // source java byte array address |
| const Register len = c_rarg2; // length |
| const Register table0 = c_rarg3; // crc_table address |
| const Register table1 = c_rarg4; |
| const Register table2 = c_rarg5; |
| const Register table3 = c_rarg6; |
| const Register tmp3 = c_rarg7; |
| |
| BLOCK_COMMENT("Entry:"); |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| |
| __ kernel_crc32c(crc, buf, len, |
| table0, table1, table2, table3, rscratch1, rscratch2, tmp3); |
| |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| /*** |
| * Arguments: |
| * |
| * Inputs: |
| * c_rarg0 - int adler |
| * c_rarg1 - byte* buff |
| * c_rarg2 - int len |
| * |
| * Output: |
| * c_rarg0 - int adler result |
| */ |
| address generate_updateBytesAdler32() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::updateBytesAdler32_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Label L_simple_by1_loop, L_nmax, L_nmax_loop, L_by16, L_by16_loop, L_by1_loop, L_do_mod, L_combine, L_by1; |
| |
| // Aliases |
| Register adler = c_rarg0; |
| Register s1 = c_rarg0; |
| Register s2 = c_rarg3; |
| Register buff = c_rarg1; |
| Register len = c_rarg2; |
| Register nmax = r4; |
| Register base = r5; |
| Register count = r6; |
| Register temp0 = rscratch1; |
| Register temp1 = rscratch2; |
| FloatRegister vbytes = v0; |
| FloatRegister vs1acc = v1; |
| FloatRegister vs2acc = v2; |
| FloatRegister vtable = v3; |
| |
| // Max number of bytes we can process before having to take the mod |
| // 0x15B0 is 5552 in decimal, the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 |
| uint64_t BASE = 0xfff1; |
| uint64_t NMAX = 0x15B0; |
| |
| __ mov(base, BASE); |
| __ mov(nmax, NMAX); |
| |
| // Load accumulation coefficients for the upper 16 bits |
| __ lea(temp0, ExternalAddress((address) StubRoutines::aarch64::_adler_table)); |
| __ ld1(vtable, __ T16B, Address(temp0)); |
| |
| // s1 is initialized to the lower 16 bits of adler |
| // s2 is initialized to the upper 16 bits of adler |
| __ ubfx(s2, adler, 16, 16); // s2 = ((adler >> 16) & 0xffff) |
| __ uxth(s1, adler); // s1 = (adler & 0xffff) |
| |
| // The pipelined loop needs at least 16 elements for 1 iteration |
| // It does check this, but it is more effective to skip to the cleanup loop |
| __ cmp(len, (u1)16); |
| __ br(Assembler::HS, L_nmax); |
| __ cbz(len, L_combine); |
| |
| __ bind(L_simple_by1_loop); |
| __ ldrb(temp0, Address(__ post(buff, 1))); |
| __ add(s1, s1, temp0); |
| __ add(s2, s2, s1); |
| __ subs(len, len, 1); |
| __ br(Assembler::HI, L_simple_by1_loop); |
| |
| // s1 = s1 % BASE |
| __ subs(temp0, s1, base); |
| __ csel(s1, temp0, s1, Assembler::HS); |
| |
| // s2 = s2 % BASE |
| __ lsr(temp0, s2, 16); |
| __ lsl(temp1, temp0, 4); |
| __ sub(temp1, temp1, temp0); |
| __ add(s2, temp1, s2, ext::uxth); |
| |
| __ subs(temp0, s2, base); |
| __ csel(s2, temp0, s2, Assembler::HS); |
| |
| __ b(L_combine); |
| |
| __ bind(L_nmax); |
| __ subs(len, len, nmax); |
| __ sub(count, nmax, 16); |
| __ br(Assembler::LO, L_by16); |
| |
| __ bind(L_nmax_loop); |
| |
| generate_updateBytesAdler32_accum(s1, s2, buff, temp0, temp1, |
| vbytes, vs1acc, vs2acc, vtable); |
| |
| __ subs(count, count, 16); |
| __ br(Assembler::HS, L_nmax_loop); |
| |
| // s1 = s1 % BASE |
| __ lsr(temp0, s1, 16); |
| __ lsl(temp1, temp0, 4); |
| __ sub(temp1, temp1, temp0); |
| __ add(temp1, temp1, s1, ext::uxth); |
| |
| __ lsr(temp0, temp1, 16); |
| __ lsl(s1, temp0, 4); |
| __ sub(s1, s1, temp0); |
| __ add(s1, s1, temp1, ext:: uxth); |
| |
| __ subs(temp0, s1, base); |
| __ csel(s1, temp0, s1, Assembler::HS); |
| |
| // s2 = s2 % BASE |
| __ lsr(temp0, s2, 16); |
| __ lsl(temp1, temp0, 4); |
| __ sub(temp1, temp1, temp0); |
| __ add(temp1, temp1, s2, ext::uxth); |
| |
| __ lsr(temp0, temp1, 16); |
| __ lsl(s2, temp0, 4); |
| __ sub(s2, s2, temp0); |
| __ add(s2, s2, temp1, ext:: uxth); |
| |
| __ subs(temp0, s2, base); |
| __ csel(s2, temp0, s2, Assembler::HS); |
| |
| __ subs(len, len, nmax); |
| __ sub(count, nmax, 16); |
| __ br(Assembler::HS, L_nmax_loop); |
| |
| __ bind(L_by16); |
| __ adds(len, len, count); |
| __ br(Assembler::LO, L_by1); |
| |
| __ bind(L_by16_loop); |
| |
| generate_updateBytesAdler32_accum(s1, s2, buff, temp0, temp1, |
| vbytes, vs1acc, vs2acc, vtable); |
| |
| __ subs(len, len, 16); |
| __ br(Assembler::HS, L_by16_loop); |
| |
| __ bind(L_by1); |
| __ adds(len, len, 15); |
| __ br(Assembler::LO, L_do_mod); |
| |
| __ bind(L_by1_loop); |
| __ ldrb(temp0, Address(__ post(buff, 1))); |
| __ add(s1, temp0, s1); |
| __ add(s2, s2, s1); |
| __ subs(len, len, 1); |
| __ br(Assembler::HS, L_by1_loop); |
| |
| __ bind(L_do_mod); |
| // s1 = s1 % BASE |
| __ lsr(temp0, s1, 16); |
| __ lsl(temp1, temp0, 4); |
| __ sub(temp1, temp1, temp0); |
| __ add(temp1, temp1, s1, ext::uxth); |
| |
| __ lsr(temp0, temp1, 16); |
| __ lsl(s1, temp0, 4); |
| __ sub(s1, s1, temp0); |
| __ add(s1, s1, temp1, ext:: uxth); |
| |
| __ subs(temp0, s1, base); |
| __ csel(s1, temp0, s1, Assembler::HS); |
| |
| // s2 = s2 % BASE |
| __ lsr(temp0, s2, 16); |
| __ lsl(temp1, temp0, 4); |
| __ sub(temp1, temp1, temp0); |
| __ add(temp1, temp1, s2, ext::uxth); |
| |
| __ lsr(temp0, temp1, 16); |
| __ lsl(s2, temp0, 4); |
| __ sub(s2, s2, temp0); |
| __ add(s2, s2, temp1, ext:: uxth); |
| |
| __ subs(temp0, s2, base); |
| __ csel(s2, temp0, s2, Assembler::HS); |
| |
| // Combine lower bits and higher bits |
| __ bind(L_combine); |
| __ orr(s1, s1, s2, Assembler::LSL, 16); // adler = s1 | (s2 << 16) |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| void generate_updateBytesAdler32_accum(Register s1, Register s2, Register buff, |
| Register temp0, Register temp1, FloatRegister vbytes, |
| FloatRegister vs1acc, FloatRegister vs2acc, FloatRegister vtable) { |
| // Below is a vectorized implementation of updating s1 and s2 for 16 bytes. |
| // We use b1, b2, ..., b16 to denote the 16 bytes loaded in each iteration. |
| // In non-vectorized code, we update s1 and s2 as: |
| // s1 <- s1 + b1 |
| // s2 <- s2 + s1 |
| // s1 <- s1 + b2 |
| // s2 <- s2 + b1 |
| // ... |
| // s1 <- s1 + b16 |
| // s2 <- s2 + s1 |
| // Putting above assignments together, we have: |
| // s1_new = s1 + b1 + b2 + ... + b16 |
| // s2_new = s2 + (s1 + b1) + (s1 + b1 + b2) + ... + (s1 + b1 + b2 + ... + b16) |
| // = s2 + s1 * 16 + (b1 * 16 + b2 * 15 + ... + b16 * 1) |
| // = s2 + s1 * 16 + (b1, b2, ... b16) dot (16, 15, ... 1) |
| __ ld1(vbytes, __ T16B, Address(__ post(buff, 16))); |
| |
| // s2 = s2 + s1 * 16 |
| __ add(s2, s2, s1, Assembler::LSL, 4); |
| |
| // vs1acc = b1 + b2 + b3 + ... + b16 |
| // vs2acc = (b1 * 16) + (b2 * 15) + (b3 * 14) + ... + (b16 * 1) |
| __ umullv(vs2acc, __ T8B, vtable, vbytes); |
| __ umlalv(vs2acc, __ T16B, vtable, vbytes); |
| __ uaddlv(vs1acc, __ T16B, vbytes); |
| __ uaddlv(vs2acc, __ T8H, vs2acc); |
| |
| // s1 = s1 + vs1acc, s2 = s2 + vs2acc |
| __ fmovd(temp0, vs1acc); |
| __ fmovd(temp1, vs2acc); |
| __ add(s1, s1, temp0); |
| __ add(s2, s2, temp1); |
| } |
| |
| /** |
| * Arguments: |
| * |
| * Input: |
| * c_rarg0 - x address |
| * c_rarg1 - x length |
| * c_rarg2 - y address |
| * c_rarg3 - y length |
| * c_rarg4 - z address |
| */ |
| address generate_multiplyToLen() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::multiplyToLen_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| const Register x = r0; |
| const Register xlen = r1; |
| const Register y = r2; |
| const Register ylen = r3; |
| const Register z = r4; |
| |
| const Register tmp0 = r5; |
| const Register tmp1 = r10; |
| const Register tmp2 = r11; |
| const Register tmp3 = r12; |
| const Register tmp4 = r13; |
| const Register tmp5 = r14; |
| const Register tmp6 = r15; |
| const Register tmp7 = r16; |
| |
| BLOCK_COMMENT("Entry:"); |
| __ enter(); // required for proper stackwalking of RuntimeStub frame |
| __ multiply_to_len(x, xlen, y, ylen, z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7); |
| __ leave(); // required for proper stackwalking of RuntimeStub frame |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| address generate_squareToLen() { |
| // squareToLen algorithm for sizes 1..127 described in java code works |
| // faster than multiply_to_len on some CPUs and slower on others, but |
| // multiply_to_len shows a bit better overall results |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::squareToLen_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| const Register x = r0; |
| const Register xlen = r1; |
| const Register z = r2; |
| const Register y = r4; // == x |
| const Register ylen = r5; // == xlen |
| |
| const Register tmp0 = r3; |
| const Register tmp1 = r10; |
| const Register tmp2 = r11; |
| const Register tmp3 = r12; |
| const Register tmp4 = r13; |
| const Register tmp5 = r14; |
| const Register tmp6 = r15; |
| const Register tmp7 = r16; |
| |
| RegSet spilled_regs = RegSet::of(y, ylen); |
| BLOCK_COMMENT("Entry:"); |
| __ enter(); |
| __ push(spilled_regs, sp); |
| __ mov(y, x); |
| __ mov(ylen, xlen); |
| __ multiply_to_len(x, xlen, y, ylen, z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7); |
| __ pop(spilled_regs, sp); |
| __ leave(); |
| __ ret(lr); |
| return start; |
| } |
| |
| address generate_mulAdd() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::mulAdd_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| |
| const Register out = r0; |
| const Register in = r1; |
| const Register offset = r2; |
| const Register len = r3; |
| const Register k = r4; |
| |
| BLOCK_COMMENT("Entry:"); |
| __ enter(); |
| __ mul_add(out, in, offset, len, k); |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Arguments: |
| // |
| // Input: |
| // c_rarg0 - newArr address |
| // c_rarg1 - oldArr address |
| // c_rarg2 - newIdx |
| // c_rarg3 - shiftCount |
| // c_rarg4 - numIter |
| // |
| address generate_bigIntegerRightShift() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::bigIntegerRightShiftWorker_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Label ShiftSIMDLoop, ShiftTwoLoop, ShiftThree, ShiftTwo, ShiftOne, Exit; |
| |
| Register newArr = c_rarg0; |
| Register oldArr = c_rarg1; |
| Register newIdx = c_rarg2; |
| Register shiftCount = c_rarg3; |
| Register numIter = c_rarg4; |
| Register idx = numIter; |
| |
| Register newArrCur = rscratch1; |
| Register shiftRevCount = rscratch2; |
| Register oldArrCur = r13; |
| Register oldArrNext = r14; |
| |
| FloatRegister oldElem0 = v0; |
| FloatRegister oldElem1 = v1; |
| FloatRegister newElem = v2; |
| FloatRegister shiftVCount = v3; |
| FloatRegister shiftVRevCount = v4; |
| |
| __ cbz(idx, Exit); |
| |
| __ add(newArr, newArr, newIdx, Assembler::LSL, 2); |
| |
| // left shift count |
| __ movw(shiftRevCount, 32); |
| __ subw(shiftRevCount, shiftRevCount, shiftCount); |
| |
| // numIter too small to allow a 4-words SIMD loop, rolling back |
| __ cmp(numIter, (u1)4); |
| __ br(Assembler::LT, ShiftThree); |
| |
| __ dup(shiftVCount, __ T4S, shiftCount); |
| __ dup(shiftVRevCount, __ T4S, shiftRevCount); |
| __ negr(shiftVCount, __ T4S, shiftVCount); |
| |
| __ BIND(ShiftSIMDLoop); |
| |
| // Calculate the load addresses |
| __ sub(idx, idx, 4); |
| __ add(oldArrNext, oldArr, idx, Assembler::LSL, 2); |
| __ add(newArrCur, newArr, idx, Assembler::LSL, 2); |
| __ add(oldArrCur, oldArrNext, 4); |
| |
| // Load 4 words and process |
| __ ld1(oldElem0, __ T4S, Address(oldArrCur)); |
| __ ld1(oldElem1, __ T4S, Address(oldArrNext)); |
| __ ushl(oldElem0, __ T4S, oldElem0, shiftVCount); |
| __ ushl(oldElem1, __ T4S, oldElem1, shiftVRevCount); |
| __ orr(newElem, __ T16B, oldElem0, oldElem1); |
| __ st1(newElem, __ T4S, Address(newArrCur)); |
| |
| __ cmp(idx, (u1)4); |
| __ br(Assembler::LT, ShiftTwoLoop); |
| __ b(ShiftSIMDLoop); |
| |
| __ BIND(ShiftTwoLoop); |
| __ cbz(idx, Exit); |
| __ cmp(idx, (u1)1); |
| __ br(Assembler::EQ, ShiftOne); |
| |
| // Calculate the load addresses |
| __ sub(idx, idx, 2); |
| __ add(oldArrNext, oldArr, idx, Assembler::LSL, 2); |
| __ add(newArrCur, newArr, idx, Assembler::LSL, 2); |
| __ add(oldArrCur, oldArrNext, 4); |
| |
| // Load 2 words and process |
| __ ld1(oldElem0, __ T2S, Address(oldArrCur)); |
| __ ld1(oldElem1, __ T2S, Address(oldArrNext)); |
| __ ushl(oldElem0, __ T2S, oldElem0, shiftVCount); |
| __ ushl(oldElem1, __ T2S, oldElem1, shiftVRevCount); |
| __ orr(newElem, __ T8B, oldElem0, oldElem1); |
| __ st1(newElem, __ T2S, Address(newArrCur)); |
| __ b(ShiftTwoLoop); |
| |
| __ BIND(ShiftThree); |
| __ tbz(idx, 1, ShiftOne); |
| __ tbz(idx, 0, ShiftTwo); |
| __ ldrw(r10, Address(oldArr, 12)); |
| __ ldrw(r11, Address(oldArr, 8)); |
| __ lsrvw(r10, r10, shiftCount); |
| __ lslvw(r11, r11, shiftRevCount); |
| __ orrw(r12, r10, r11); |
| __ strw(r12, Address(newArr, 8)); |
| |
| __ BIND(ShiftTwo); |
| __ ldrw(r10, Address(oldArr, 8)); |
| __ ldrw(r11, Address(oldArr, 4)); |
| __ lsrvw(r10, r10, shiftCount); |
| __ lslvw(r11, r11, shiftRevCount); |
| __ orrw(r12, r10, r11); |
| __ strw(r12, Address(newArr, 4)); |
| |
| __ BIND(ShiftOne); |
| __ ldrw(r10, Address(oldArr, 4)); |
| __ ldrw(r11, Address(oldArr)); |
| __ lsrvw(r10, r10, shiftCount); |
| __ lslvw(r11, r11, shiftRevCount); |
| __ orrw(r12, r10, r11); |
| __ strw(r12, Address(newArr)); |
| |
| __ BIND(Exit); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Arguments: |
| // |
| // Input: |
| // c_rarg0 - newArr address |
| // c_rarg1 - oldArr address |
| // c_rarg2 - newIdx |
| // c_rarg3 - shiftCount |
| // c_rarg4 - numIter |
| // |
| address generate_bigIntegerLeftShift() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::bigIntegerLeftShiftWorker_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Label ShiftSIMDLoop, ShiftTwoLoop, ShiftThree, ShiftTwo, ShiftOne, Exit; |
| |
| Register newArr = c_rarg0; |
| Register oldArr = c_rarg1; |
| Register newIdx = c_rarg2; |
| Register shiftCount = c_rarg3; |
| Register numIter = c_rarg4; |
| |
| Register shiftRevCount = rscratch1; |
| Register oldArrNext = rscratch2; |
| |
| FloatRegister oldElem0 = v0; |
| FloatRegister oldElem1 = v1; |
| FloatRegister newElem = v2; |
| FloatRegister shiftVCount = v3; |
| FloatRegister shiftVRevCount = v4; |
| |
| __ cbz(numIter, Exit); |
| |
| __ add(oldArrNext, oldArr, 4); |
| __ add(newArr, newArr, newIdx, Assembler::LSL, 2); |
| |
| // right shift count |
| __ movw(shiftRevCount, 32); |
| __ subw(shiftRevCount, shiftRevCount, shiftCount); |
| |
| // numIter too small to allow a 4-words SIMD loop, rolling back |
| __ cmp(numIter, (u1)4); |
| __ br(Assembler::LT, ShiftThree); |
| |
| __ dup(shiftVCount, __ T4S, shiftCount); |
| __ dup(shiftVRevCount, __ T4S, shiftRevCount); |
| __ negr(shiftVRevCount, __ T4S, shiftVRevCount); |
| |
| __ BIND(ShiftSIMDLoop); |
| |
| // load 4 words and process |
| __ ld1(oldElem0, __ T4S, __ post(oldArr, 16)); |
| __ ld1(oldElem1, __ T4S, __ post(oldArrNext, 16)); |
| __ ushl(oldElem0, __ T4S, oldElem0, shiftVCount); |
| __ ushl(oldElem1, __ T4S, oldElem1, shiftVRevCount); |
| __ orr(newElem, __ T16B, oldElem0, oldElem1); |
| __ st1(newElem, __ T4S, __ post(newArr, 16)); |
| __ sub(numIter, numIter, 4); |
| |
| __ cmp(numIter, (u1)4); |
| __ br(Assembler::LT, ShiftTwoLoop); |
| __ b(ShiftSIMDLoop); |
| |
| __ BIND(ShiftTwoLoop); |
| __ cbz(numIter, Exit); |
| __ cmp(numIter, (u1)1); |
| __ br(Assembler::EQ, ShiftOne); |
| |
| // load 2 words and process |
| __ ld1(oldElem0, __ T2S, __ post(oldArr, 8)); |
| __ ld1(oldElem1, __ T2S, __ post(oldArrNext, 8)); |
| __ ushl(oldElem0, __ T2S, oldElem0, shiftVCount); |
| __ ushl(oldElem1, __ T2S, oldElem1, shiftVRevCount); |
| __ orr(newElem, __ T8B, oldElem0, oldElem1); |
| __ st1(newElem, __ T2S, __ post(newArr, 8)); |
| __ sub(numIter, numIter, 2); |
| __ b(ShiftTwoLoop); |
| |
| __ BIND(ShiftThree); |
| __ ldrw(r10, __ post(oldArr, 4)); |
| __ ldrw(r11, __ post(oldArrNext, 4)); |
| __ lslvw(r10, r10, shiftCount); |
| __ lsrvw(r11, r11, shiftRevCount); |
| __ orrw(r12, r10, r11); |
| __ strw(r12, __ post(newArr, 4)); |
| __ tbz(numIter, 1, Exit); |
| __ tbz(numIter, 0, ShiftOne); |
| |
| __ BIND(ShiftTwo); |
| __ ldrw(r10, __ post(oldArr, 4)); |
| __ ldrw(r11, __ post(oldArrNext, 4)); |
| __ lslvw(r10, r10, shiftCount); |
| __ lsrvw(r11, r11, shiftRevCount); |
| __ orrw(r12, r10, r11); |
| __ strw(r12, __ post(newArr, 4)); |
| |
| __ BIND(ShiftOne); |
| __ ldrw(r10, Address(oldArr)); |
| __ ldrw(r11, Address(oldArrNext)); |
| __ lslvw(r10, r10, shiftCount); |
| __ lsrvw(r11, r11, shiftRevCount); |
| __ orrw(r12, r10, r11); |
| __ strw(r12, Address(newArr)); |
| |
| __ BIND(Exit); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| address generate_count_positives(address &count_positives_long) { |
| const u1 large_loop_size = 64; |
| const uint64_t UPPER_BIT_MASK=0x8080808080808080; |
| int dcache_line = VM_Version::dcache_line_size(); |
| |
| Register ary1 = r1, len = r2, result = r0; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubGenStubId stub_id = StubGenStubId::count_positives_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address entry = __ pc(); |
| |
| __ enter(); |
| // precondition: a copy of len is already in result |
| // __ mov(result, len); |
| |
| Label RET_ADJUST, RET_ADJUST_16, RET_ADJUST_LONG, RET_NO_POP, RET_LEN, ALIGNED, LOOP16, CHECK_16, |
| LARGE_LOOP, POST_LOOP16, LEN_OVER_15, LEN_OVER_8, POST_LOOP16_LOAD_TAIL; |
| |
| __ cmp(len, (u1)15); |
| __ br(Assembler::GT, LEN_OVER_15); |
| // The only case when execution falls into this code is when pointer is near |
| // the end of memory page and we have to avoid reading next page |
| __ add(ary1, ary1, len); |
| __ subs(len, len, 8); |
| __ br(Assembler::GT, LEN_OVER_8); |
| __ ldr(rscratch2, Address(ary1, -8)); |
| __ sub(rscratch1, zr, len, __ LSL, 3); // LSL 3 is to get bits from bytes. |
| __ lsrv(rscratch2, rscratch2, rscratch1); |
| __ tst(rscratch2, UPPER_BIT_MASK); |
| __ csel(result, zr, result, Assembler::NE); |
| __ leave(); |
| __ ret(lr); |
| __ bind(LEN_OVER_8); |
| __ ldp(rscratch1, rscratch2, Address(ary1, -16)); |
| __ sub(len, len, 8); // no data dep., then sub can be executed while loading |
| __ tst(rscratch2, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_NO_POP); |
| __ sub(rscratch2, zr, len, __ LSL, 3); // LSL 3 is to get bits from bytes |
| __ lsrv(rscratch1, rscratch1, rscratch2); |
| __ tst(rscratch1, UPPER_BIT_MASK); |
| __ bind(RET_NO_POP); |
| __ csel(result, zr, result, Assembler::NE); |
| __ leave(); |
| __ ret(lr); |
| |
| Register tmp1 = r3, tmp2 = r4, tmp3 = r5, tmp4 = r6, tmp5 = r7, tmp6 = r10; |
| const RegSet spilled_regs = RegSet::range(tmp1, tmp5) + tmp6; |
| |
| count_positives_long = __ pc(); // 2nd entry point |
| |
| __ enter(); |
| |
| __ bind(LEN_OVER_15); |
| __ push(spilled_regs, sp); |
| __ andr(rscratch2, ary1, 15); // check pointer for 16-byte alignment |
| __ cbz(rscratch2, ALIGNED); |
| __ ldp(tmp6, tmp1, Address(ary1)); |
| __ mov(tmp5, 16); |
| __ sub(rscratch1, tmp5, rscratch2); // amount of bytes until aligned address |
| __ add(ary1, ary1, rscratch1); |
| __ orr(tmp6, tmp6, tmp1); |
| __ tst(tmp6, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_ADJUST); |
| __ sub(len, len, rscratch1); |
| |
| __ bind(ALIGNED); |
| __ cmp(len, large_loop_size); |
| __ br(Assembler::LT, CHECK_16); |
| // Perform 16-byte load as early return in pre-loop to handle situation |
| // when initially aligned large array has negative values at starting bytes, |
| // so LARGE_LOOP would do 4 reads instead of 1 (in worst case), which is |
| // slower. Cases with negative bytes further ahead won't be affected that |
| // much. In fact, it'll be faster due to early loads, less instructions and |
| // less branches in LARGE_LOOP. |
| __ ldp(tmp6, tmp1, Address(__ post(ary1, 16))); |
| __ sub(len, len, 16); |
| __ orr(tmp6, tmp6, tmp1); |
| __ tst(tmp6, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_ADJUST_16); |
| __ cmp(len, large_loop_size); |
| __ br(Assembler::LT, CHECK_16); |
| |
| if (SoftwarePrefetchHintDistance >= 0 |
| && SoftwarePrefetchHintDistance >= dcache_line) { |
| // initial prefetch |
| __ prfm(Address(ary1, SoftwarePrefetchHintDistance - dcache_line)); |
| } |
| __ bind(LARGE_LOOP); |
| if (SoftwarePrefetchHintDistance >= 0) { |
| __ prfm(Address(ary1, SoftwarePrefetchHintDistance)); |
| } |
| // Issue load instructions first, since it can save few CPU/MEM cycles, also |
| // instead of 4 triples of "orr(...), addr(...);cbnz(...);" (for each ldp) |
| // better generate 7 * orr(...) + 1 andr(...) + 1 cbnz(...) which saves 3 |
| // instructions per cycle and have less branches, but this approach disables |
| // early return, thus, all 64 bytes are loaded and checked every time. |
| __ ldp(tmp2, tmp3, Address(ary1)); |
| __ ldp(tmp4, tmp5, Address(ary1, 16)); |
| __ ldp(rscratch1, rscratch2, Address(ary1, 32)); |
| __ ldp(tmp6, tmp1, Address(ary1, 48)); |
| __ add(ary1, ary1, large_loop_size); |
| __ sub(len, len, large_loop_size); |
| __ orr(tmp2, tmp2, tmp3); |
| __ orr(tmp4, tmp4, tmp5); |
| __ orr(rscratch1, rscratch1, rscratch2); |
| __ orr(tmp6, tmp6, tmp1); |
| __ orr(tmp2, tmp2, tmp4); |
| __ orr(rscratch1, rscratch1, tmp6); |
| __ orr(tmp2, tmp2, rscratch1); |
| __ tst(tmp2, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_ADJUST_LONG); |
| __ cmp(len, large_loop_size); |
| __ br(Assembler::GE, LARGE_LOOP); |
| |
| __ bind(CHECK_16); // small 16-byte load pre-loop |
| __ cmp(len, (u1)16); |
| __ br(Assembler::LT, POST_LOOP16); |
| |
| __ bind(LOOP16); // small 16-byte load loop |
| __ ldp(tmp2, tmp3, Address(__ post(ary1, 16))); |
| __ sub(len, len, 16); |
| __ orr(tmp2, tmp2, tmp3); |
| __ tst(tmp2, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_ADJUST_16); |
| __ cmp(len, (u1)16); |
| __ br(Assembler::GE, LOOP16); // 16-byte load loop end |
| |
| __ bind(POST_LOOP16); // 16-byte aligned, so we can read unconditionally |
| __ cmp(len, (u1)8); |
| __ br(Assembler::LE, POST_LOOP16_LOAD_TAIL); |
| __ ldr(tmp3, Address(__ post(ary1, 8))); |
| __ tst(tmp3, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_ADJUST); |
| __ sub(len, len, 8); |
| |
| __ bind(POST_LOOP16_LOAD_TAIL); |
| __ cbz(len, RET_LEN); // Can't shift left by 64 when len==0 |
| __ ldr(tmp1, Address(ary1)); |
| __ mov(tmp2, 64); |
| __ sub(tmp4, tmp2, len, __ LSL, 3); |
| __ lslv(tmp1, tmp1, tmp4); |
| __ tst(tmp1, UPPER_BIT_MASK); |
| __ br(Assembler::NE, RET_ADJUST); |
| // Fallthrough |
| |
| __ bind(RET_LEN); |
| __ pop(spilled_regs, sp); |
| __ leave(); |
| __ ret(lr); |
| |
| // difference result - len is the count of guaranteed to be |
| // positive bytes |
| |
| __ bind(RET_ADJUST_LONG); |
| __ add(len, len, (u1)(large_loop_size - 16)); |
| __ bind(RET_ADJUST_16); |
| __ add(len, len, 16); |
| __ bind(RET_ADJUST); |
| __ pop(spilled_regs, sp); |
| __ leave(); |
| __ sub(result, result, len); |
| __ ret(lr); |
| |
| return entry; |
| } |
| |
| void generate_large_array_equals_loop_nonsimd(int loopThreshold, |
| bool usePrefetch, Label &NOT_EQUAL) { |
| Register a1 = r1, a2 = r2, result = r0, cnt1 = r10, tmp1 = rscratch1, |
| tmp2 = rscratch2, tmp3 = r3, tmp4 = r4, tmp5 = r5, tmp6 = r11, |
| tmp7 = r12, tmp8 = r13; |
| Label LOOP; |
| |
| __ ldp(tmp1, tmp3, Address(__ post(a1, 2 * wordSize))); |
| __ ldp(tmp2, tmp4, Address(__ post(a2, 2 * wordSize))); |
| __ bind(LOOP); |
| if (usePrefetch) { |
| __ prfm(Address(a1, SoftwarePrefetchHintDistance)); |
| __ prfm(Address(a2, SoftwarePrefetchHintDistance)); |
| } |
| __ ldp(tmp5, tmp7, Address(__ post(a1, 2 * wordSize))); |
| __ eor(tmp1, tmp1, tmp2); |
| __ eor(tmp3, tmp3, tmp4); |
| __ ldp(tmp6, tmp8, Address(__ post(a2, 2 * wordSize))); |
| __ orr(tmp1, tmp1, tmp3); |
| __ cbnz(tmp1, NOT_EQUAL); |
| __ ldp(tmp1, tmp3, Address(__ post(a1, 2 * wordSize))); |
| __ eor(tmp5, tmp5, tmp6); |
| __ eor(tmp7, tmp7, tmp8); |
| __ ldp(tmp2, tmp4, Address(__ post(a2, 2 * wordSize))); |
| __ orr(tmp5, tmp5, tmp7); |
| __ cbnz(tmp5, NOT_EQUAL); |
| __ ldp(tmp5, tmp7, Address(__ post(a1, 2 * wordSize))); |
| __ eor(tmp1, tmp1, tmp2); |
| __ eor(tmp3, tmp3, tmp4); |
| __ ldp(tmp6, tmp8, Address(__ post(a2, 2 * wordSize))); |
| __ orr(tmp1, tmp1, tmp3); |
| __ cbnz(tmp1, NOT_EQUAL); |
| __ ldp(tmp1, tmp3, Address(__ post(a1, 2 * wordSize))); |
| __ eor(tmp5, tmp5, tmp6); |
| __ sub(cnt1, cnt1, 8 * wordSize); |
| __ eor(tmp7, tmp7, tmp8); |
| __ ldp(tmp2, tmp4, Address(__ post(a2, 2 * wordSize))); |
| // tmp6 is not used. MacroAssembler::subs is used here (rather than |
| // cmp) because subs allows an unlimited range of immediate operand. |
| __ subs(tmp6, cnt1, loopThreshold); |
| __ orr(tmp5, tmp5, tmp7); |
| __ cbnz(tmp5, NOT_EQUAL); |
| __ br(__ GE, LOOP); |
| // post-loop |
| __ eor(tmp1, tmp1, tmp2); |
| __ eor(tmp3, tmp3, tmp4); |
| __ orr(tmp1, tmp1, tmp3); |
| __ sub(cnt1, cnt1, 2 * wordSize); |
| __ cbnz(tmp1, NOT_EQUAL); |
| } |
| |
| void generate_large_array_equals_loop_simd(int loopThreshold, |
| bool usePrefetch, Label &NOT_EQUAL) { |
| Register a1 = r1, a2 = r2, result = r0, cnt1 = r10, tmp1 = rscratch1, |
| tmp2 = rscratch2; |
| Label LOOP; |
| |
| __ bind(LOOP); |
| if (usePrefetch) { |
| __ prfm(Address(a1, SoftwarePrefetchHintDistance)); |
| __ prfm(Address(a2, SoftwarePrefetchHintDistance)); |
| } |
| __ ld1(v0, v1, v2, v3, __ T2D, Address(__ post(a1, 4 * 2 * wordSize))); |
| __ sub(cnt1, cnt1, 8 * wordSize); |
| __ ld1(v4, v5, v6, v7, __ T2D, Address(__ post(a2, 4 * 2 * wordSize))); |
| __ subs(tmp1, cnt1, loopThreshold); |
| __ eor(v0, __ T16B, v0, v4); |
| __ eor(v1, __ T16B, v1, v5); |
| __ eor(v2, __ T16B, v2, v6); |
| __ eor(v3, __ T16B, v3, v7); |
| __ orr(v0, __ T16B, v0, v1); |
| __ orr(v1, __ T16B, v2, v3); |
| __ orr(v0, __ T16B, v0, v1); |
| __ umov(tmp1, v0, __ D, 0); |
| __ umov(tmp2, v0, __ D, 1); |
| __ orr(tmp1, tmp1, tmp2); |
| __ cbnz(tmp1, NOT_EQUAL); |
| __ br(__ GE, LOOP); |
| } |
| |
| // a1 = r1 - array1 address |
| // a2 = r2 - array2 address |
| // result = r0 - return value. Already contains "false" |
| // cnt1 = r10 - amount of elements left to check, reduced by wordSize |
| // r3-r5 are reserved temporary registers |
| // Clobbers: v0-v7 when UseSIMDForArrayEquals, rscratch1, rscratch2 |
| address generate_large_array_equals() { |
| Register a1 = r1, a2 = r2, result = r0, cnt1 = r10, tmp1 = rscratch1, |
| tmp2 = rscratch2, tmp3 = r3, tmp4 = r4, tmp5 = r5, tmp6 = r11, |
| tmp7 = r12, tmp8 = r13; |
| Label TAIL, NOT_EQUAL, EQUAL, NOT_EQUAL_NO_POP, NO_PREFETCH_LARGE_LOOP, |
| SMALL_LOOP, POST_LOOP; |
| const int PRE_LOOP_SIZE = UseSIMDForArrayEquals ? 0 : 16; |
| // calculate if at least 32 prefetched bytes are used |
| int prefetchLoopThreshold = SoftwarePrefetchHintDistance + 32; |
| int nonPrefetchLoopThreshold = (64 + PRE_LOOP_SIZE); |
| RegSet spilled_regs = RegSet::range(tmp6, tmp8); |
| assert_different_registers(a1, a2, result, cnt1, tmp1, tmp2, tmp3, tmp4, |
| tmp5, tmp6, tmp7, tmp8); |
| |
| __ align(CodeEntryAlignment); |
| |
| StubGenStubId stub_id = StubGenStubId::large_array_equals_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address entry = __ pc(); |
| __ enter(); |
| __ sub(cnt1, cnt1, wordSize); // first 8 bytes were loaded outside of stub |
| // also advance pointers to use post-increment instead of pre-increment |
| __ add(a1, a1, wordSize); |
| __ add(a2, a2, wordSize); |
| if (AvoidUnalignedAccesses) { |
| // both implementations (SIMD/nonSIMD) are using relatively large load |
| // instructions (ld1/ldp), which has huge penalty (up to x2 exec time) |
| // on some CPUs in case of address is not at least 16-byte aligned. |
| // Arrays are 8-byte aligned currently, so, we can make additional 8-byte |
| // load if needed at least for 1st address and make if 16-byte aligned. |
| Label ALIGNED16; |
| __ tbz(a1, 3, ALIGNED16); |
| __ ldr(tmp1, Address(__ post(a1, wordSize))); |
| __ ldr(tmp2, Address(__ post(a2, wordSize))); |
| __ sub(cnt1, cnt1, wordSize); |
| __ eor(tmp1, tmp1, tmp2); |
| __ cbnz(tmp1, NOT_EQUAL_NO_POP); |
| __ bind(ALIGNED16); |
| } |
| if (UseSIMDForArrayEquals) { |
| if (SoftwarePrefetchHintDistance >= 0) { |
| __ subs(tmp1, cnt1, prefetchLoopThreshold); |
| __ br(__ LE, NO_PREFETCH_LARGE_LOOP); |
| generate_large_array_equals_loop_simd(prefetchLoopThreshold, |
| /* prfm = */ true, NOT_EQUAL); |
| __ subs(zr, cnt1, nonPrefetchLoopThreshold); |
| __ br(__ LT, TAIL); |
| } |
| __ bind(NO_PREFETCH_LARGE_LOOP); |
| generate_large_array_equals_loop_simd(nonPrefetchLoopThreshold, |
| /* prfm = */ false, NOT_EQUAL); |
| } else { |
| __ push(spilled_regs, sp); |
| if (SoftwarePrefetchHintDistance >= 0) { |
| __ subs(tmp1, cnt1, prefetchLoopThreshold); |
| __ br(__ LE, NO_PREFETCH_LARGE_LOOP); |
| generate_large_array_equals_loop_nonsimd(prefetchLoopThreshold, |
| /* prfm = */ true, NOT_EQUAL); |
| __ subs(zr, cnt1, nonPrefetchLoopThreshold); |
| __ br(__ LT, TAIL); |
| } |
| __ bind(NO_PREFETCH_LARGE_LOOP); |
| generate_large_array_equals_loop_nonsimd(nonPrefetchLoopThreshold, |
| /* prfm = */ false, NOT_EQUAL); |
| } |
| __ bind(TAIL); |
| __ cbz(cnt1, EQUAL); |
| __ subs(cnt1, cnt1, wordSize); |
| __ br(__ LE, POST_LOOP); |
| __ bind(SMALL_LOOP); |
| __ ldr(tmp1, Address(__ post(a1, wordSize))); |
| __ ldr(tmp2, Address(__ post(a2, wordSize))); |
| __ subs(cnt1, cnt1, wordSize); |
| __ eor(tmp1, tmp1, tmp2); |
| __ cbnz(tmp1, NOT_EQUAL); |
| __ br(__ GT, SMALL_LOOP); |
| __ bind(POST_LOOP); |
| __ ldr(tmp1, Address(a1, cnt1)); |
| __ ldr(tmp2, Address(a2, cnt1)); |
| __ eor(tmp1, tmp1, tmp2); |
| __ cbnz(tmp1, NOT_EQUAL); |
| __ bind(EQUAL); |
| __ mov(result, true); |
| __ bind(NOT_EQUAL); |
| if (!UseSIMDForArrayEquals) { |
| __ pop(spilled_regs, sp); |
| } |
| __ bind(NOT_EQUAL_NO_POP); |
| __ leave(); |
| __ ret(lr); |
| return entry; |
| } |
| |
| // result = r0 - return value. Contains initial hashcode value on entry. |
| // ary = r1 - array address |
| // cnt = r2 - elements count |
| // Clobbers: v0-v13, rscratch1, rscratch2 |
| address generate_large_arrays_hashcode(BasicType eltype) { |
| const Register result = r0, ary = r1, cnt = r2; |
| const FloatRegister vdata0 = v3, vdata1 = v2, vdata2 = v1, vdata3 = v0; |
| const FloatRegister vmul0 = v4, vmul1 = v5, vmul2 = v6, vmul3 = v7; |
| const FloatRegister vpow = v12; // powers of 31: <31^3, ..., 31^0> |
| const FloatRegister vpowm = v13; |
| |
| ARRAYS_HASHCODE_REGISTERS; |
| |
| Label SMALL_LOOP, LARGE_LOOP_PREHEADER, LARGE_LOOP, TAIL, TAIL_SHORTCUT, BR_BASE; |
| |
| unsigned int vf; // vectorization factor |
| bool multiply_by_halves; |
| Assembler::SIMD_Arrangement load_arrangement; |
| switch (eltype) { |
| case T_BOOLEAN: |
| case T_BYTE: |
| load_arrangement = Assembler::T8B; |
| multiply_by_halves = true; |
| vf = 8; |
| break; |
| case T_CHAR: |
| case T_SHORT: |
| load_arrangement = Assembler::T8H; |
| multiply_by_halves = true; |
| vf = 8; |
| break; |
| case T_INT: |
| load_arrangement = Assembler::T4S; |
| multiply_by_halves = false; |
| vf = 4; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| // Unroll factor |
| const unsigned uf = 4; |
| |
| // Effective vectorization factor |
| const unsigned evf = vf * uf; |
| |
| __ align(CodeEntryAlignment); |
| |
| StubGenStubId stub_id; |
| switch (eltype) { |
| case T_BOOLEAN: |
| stub_id = StubGenStubId::large_arrays_hashcode_boolean_id; |
| break; |
| case T_BYTE: |
| stub_id = StubGenStubId::large_arrays_hashcode_byte_id; |
| break; |
| case T_CHAR: |
| stub_id = StubGenStubId::large_arrays_hashcode_char_id; |
| break; |
| case T_SHORT: |
| stub_id = StubGenStubId::large_arrays_hashcode_short_id; |
| break; |
| case T_INT: |
| stub_id = StubGenStubId::large_arrays_hashcode_int_id; |
| break; |
| default: |
| stub_id = StubGenStubId::NO_STUBID; |
| ShouldNotReachHere(); |
| }; |
| |
| StubCodeMark mark(this, stub_id); |
| |
| address entry = __ pc(); |
| __ enter(); |
| |
| // Put 0-3'th powers of 31 into a single SIMD register together. The register will be used in |
| // the SMALL and LARGE LOOPS' epilogues. The initialization is hoisted here and the register's |
| // value shouldn't change throughout both loops. |
| __ movw(rscratch1, intpow(31U, 3)); |
| __ mov(vpow, Assembler::S, 0, rscratch1); |
| __ movw(rscratch1, intpow(31U, 2)); |
| __ mov(vpow, Assembler::S, 1, rscratch1); |
| __ movw(rscratch1, intpow(31U, 1)); |
| __ mov(vpow, Assembler::S, 2, rscratch1); |
| __ movw(rscratch1, intpow(31U, 0)); |
| __ mov(vpow, Assembler::S, 3, rscratch1); |
| |
| __ mov(vmul0, Assembler::T16B, 0); |
| __ mov(vmul0, Assembler::S, 3, result); |
| |
| __ andr(rscratch2, cnt, (uf - 1) * vf); |
| __ cbz(rscratch2, LARGE_LOOP_PREHEADER); |
| |
| __ movw(rscratch1, intpow(31U, multiply_by_halves ? vf / 2 : vf)); |
| __ mov(vpowm, Assembler::S, 0, rscratch1); |
| |
| // SMALL LOOP |
| __ bind(SMALL_LOOP); |
| |
| __ ld1(vdata0, load_arrangement, Address(__ post(ary, vf * type2aelembytes(eltype)))); |
| __ mulvs(vmul0, Assembler::T4S, vmul0, vpowm, 0); |
| __ subsw(rscratch2, rscratch2, vf); |
| |
| if (load_arrangement == Assembler::T8B) { |
| // Extend 8B to 8H to be able to use vector multiply |
| // instructions |
| assert(load_arrangement == Assembler::T8B, "expected to extend 8B to 8H"); |
| if (is_signed_subword_type(eltype)) { |
| __ sxtl(vdata0, Assembler::T8H, vdata0, load_arrangement); |
| } else { |
| __ uxtl(vdata0, Assembler::T8H, vdata0, load_arrangement); |
| } |
| } |
| |
| switch (load_arrangement) { |
| case Assembler::T4S: |
| __ addv(vmul0, load_arrangement, vmul0, vdata0); |
| break; |
| case Assembler::T8B: |
| case Assembler::T8H: |
| assert(is_subword_type(eltype), "subword type expected"); |
| if (is_signed_subword_type(eltype)) { |
| __ saddwv(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T4H); |
| } else { |
| __ uaddwv(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T4H); |
| } |
| break; |
| default: |
| __ should_not_reach_here(); |
| } |
| |
| // Process the upper half of a vector |
| if (load_arrangement == Assembler::T8B || load_arrangement == Assembler::T8H) { |
| __ mulvs(vmul0, Assembler::T4S, vmul0, vpowm, 0); |
| if (is_signed_subword_type(eltype)) { |
| __ saddwv2(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T8H); |
| } else { |
| __ uaddwv2(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T8H); |
| } |
| } |
| |
| __ br(Assembler::HI, SMALL_LOOP); |
| |
| // SMALL LOOP'S EPILOQUE |
| __ lsr(rscratch2, cnt, exact_log2(evf)); |
| __ cbnz(rscratch2, LARGE_LOOP_PREHEADER); |
| |
| __ mulv(vmul0, Assembler::T4S, vmul0, vpow); |
| __ addv(vmul0, Assembler::T4S, vmul0); |
| __ umov(result, vmul0, Assembler::S, 0); |
| |
| // TAIL |
| __ bind(TAIL); |
| |
| // The andr performs cnt % vf. The subtract shifted by 3 offsets past vf - 1 - (cnt % vf) pairs |
| // of load + madd insns i.e. it only executes cnt % vf load + madd pairs. |
| assert(is_power_of_2(vf), "can't use this value to calculate the jump target PC"); |
| __ andr(rscratch2, cnt, vf - 1); |
| __ bind(TAIL_SHORTCUT); |
| __ adr(rscratch1, BR_BASE); |
| // For Cortex-A53 offset is 4 because 2 nops are generated. |
| __ sub(rscratch1, rscratch1, rscratch2, ext::uxtw, VM_Version::supports_a53mac() ? 4 : 3); |
| __ movw(rscratch2, 0x1f); |
| __ br(rscratch1); |
| |
| for (size_t i = 0; i < vf - 1; ++i) { |
| __ load(rscratch1, Address(__ post(ary, type2aelembytes(eltype))), |
| eltype); |
| __ maddw(result, result, rscratch2, rscratch1); |
| // maddw generates an extra nop for Cortex-A53 (see maddw definition in macroAssembler). |
| // Generate 2nd nop to have 4 instructions per iteration. |
| if (VM_Version::supports_a53mac()) { |
| __ nop(); |
| } |
| } |
| __ bind(BR_BASE); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| // LARGE LOOP |
| __ bind(LARGE_LOOP_PREHEADER); |
| |
| __ lsr(rscratch2, cnt, exact_log2(evf)); |
| |
| if (multiply_by_halves) { |
| // 31^4 - multiplier between lower and upper parts of a register |
| __ movw(rscratch1, intpow(31U, vf / 2)); |
| __ mov(vpowm, Assembler::S, 1, rscratch1); |
| // 31^28 - remainder of the iteraion multiplier, 28 = 32 - 4 |
| __ movw(rscratch1, intpow(31U, evf - vf / 2)); |
| __ mov(vpowm, Assembler::S, 0, rscratch1); |
| } else { |
| // 31^16 |
| __ movw(rscratch1, intpow(31U, evf)); |
| __ mov(vpowm, Assembler::S, 0, rscratch1); |
| } |
| |
| __ mov(vmul3, Assembler::T16B, 0); |
| __ mov(vmul2, Assembler::T16B, 0); |
| __ mov(vmul1, Assembler::T16B, 0); |
| |
| __ bind(LARGE_LOOP); |
| |
| __ mulvs(vmul3, Assembler::T4S, vmul3, vpowm, 0); |
| __ mulvs(vmul2, Assembler::T4S, vmul2, vpowm, 0); |
| __ mulvs(vmul1, Assembler::T4S, vmul1, vpowm, 0); |
| __ mulvs(vmul0, Assembler::T4S, vmul0, vpowm, 0); |
| |
| __ ld1(vdata3, vdata2, vdata1, vdata0, load_arrangement, |
| Address(__ post(ary, evf * type2aelembytes(eltype)))); |
| |
| if (load_arrangement == Assembler::T8B) { |
| // Extend 8B to 8H to be able to use vector multiply |
| // instructions |
| assert(load_arrangement == Assembler::T8B, "expected to extend 8B to 8H"); |
| if (is_signed_subword_type(eltype)) { |
| __ sxtl(vdata3, Assembler::T8H, vdata3, load_arrangement); |
| __ sxtl(vdata2, Assembler::T8H, vdata2, load_arrangement); |
| __ sxtl(vdata1, Assembler::T8H, vdata1, load_arrangement); |
| __ sxtl(vdata0, Assembler::T8H, vdata0, load_arrangement); |
| } else { |
| __ uxtl(vdata3, Assembler::T8H, vdata3, load_arrangement); |
| __ uxtl(vdata2, Assembler::T8H, vdata2, load_arrangement); |
| __ uxtl(vdata1, Assembler::T8H, vdata1, load_arrangement); |
| __ uxtl(vdata0, Assembler::T8H, vdata0, load_arrangement); |
| } |
| } |
| |
| switch (load_arrangement) { |
| case Assembler::T4S: |
| __ addv(vmul3, load_arrangement, vmul3, vdata3); |
| __ addv(vmul2, load_arrangement, vmul2, vdata2); |
| __ addv(vmul1, load_arrangement, vmul1, vdata1); |
| __ addv(vmul0, load_arrangement, vmul0, vdata0); |
| break; |
| case Assembler::T8B: |
| case Assembler::T8H: |
| assert(is_subword_type(eltype), "subword type expected"); |
| if (is_signed_subword_type(eltype)) { |
| __ saddwv(vmul3, vmul3, Assembler::T4S, vdata3, Assembler::T4H); |
| __ saddwv(vmul2, vmul2, Assembler::T4S, vdata2, Assembler::T4H); |
| __ saddwv(vmul1, vmul1, Assembler::T4S, vdata1, Assembler::T4H); |
| __ saddwv(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T4H); |
| } else { |
| __ uaddwv(vmul3, vmul3, Assembler::T4S, vdata3, Assembler::T4H); |
| __ uaddwv(vmul2, vmul2, Assembler::T4S, vdata2, Assembler::T4H); |
| __ uaddwv(vmul1, vmul1, Assembler::T4S, vdata1, Assembler::T4H); |
| __ uaddwv(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T4H); |
| } |
| break; |
| default: |
| __ should_not_reach_here(); |
| } |
| |
| // Process the upper half of a vector |
| if (load_arrangement == Assembler::T8B || load_arrangement == Assembler::T8H) { |
| __ mulvs(vmul3, Assembler::T4S, vmul3, vpowm, 1); |
| __ mulvs(vmul2, Assembler::T4S, vmul2, vpowm, 1); |
| __ mulvs(vmul1, Assembler::T4S, vmul1, vpowm, 1); |
| __ mulvs(vmul0, Assembler::T4S, vmul0, vpowm, 1); |
| if (is_signed_subword_type(eltype)) { |
| __ saddwv2(vmul3, vmul3, Assembler::T4S, vdata3, Assembler::T8H); |
| __ saddwv2(vmul2, vmul2, Assembler::T4S, vdata2, Assembler::T8H); |
| __ saddwv2(vmul1, vmul1, Assembler::T4S, vdata1, Assembler::T8H); |
| __ saddwv2(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T8H); |
| } else { |
| __ uaddwv2(vmul3, vmul3, Assembler::T4S, vdata3, Assembler::T8H); |
| __ uaddwv2(vmul2, vmul2, Assembler::T4S, vdata2, Assembler::T8H); |
| __ uaddwv2(vmul1, vmul1, Assembler::T4S, vdata1, Assembler::T8H); |
| __ uaddwv2(vmul0, vmul0, Assembler::T4S, vdata0, Assembler::T8H); |
| } |
| } |
| |
| __ subsw(rscratch2, rscratch2, 1); |
| __ br(Assembler::HI, LARGE_LOOP); |
| |
| __ mulv(vmul3, Assembler::T4S, vmul3, vpow); |
| __ addv(vmul3, Assembler::T4S, vmul3); |
| __ umov(result, vmul3, Assembler::S, 0); |
| |
| __ mov(rscratch2, intpow(31U, vf)); |
| |
| __ mulv(vmul2, Assembler::T4S, vmul2, vpow); |
| __ addv(vmul2, Assembler::T4S, vmul2); |
| __ umov(rscratch1, vmul2, Assembler::S, 0); |
| __ maddw(result, result, rscratch2, rscratch1); |
| |
| __ mulv(vmul1, Assembler::T4S, vmul1, vpow); |
| __ addv(vmul1, Assembler::T4S, vmul1); |
| __ umov(rscratch1, vmul1, Assembler::S, 0); |
| __ maddw(result, result, rscratch2, rscratch1); |
| |
| __ mulv(vmul0, Assembler::T4S, vmul0, vpow); |
| __ addv(vmul0, Assembler::T4S, vmul0); |
| __ umov(rscratch1, vmul0, Assembler::S, 0); |
| __ maddw(result, result, rscratch2, rscratch1); |
| |
| __ andr(rscratch2, cnt, vf - 1); |
| __ cbnz(rscratch2, TAIL_SHORTCUT); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| return entry; |
| } |
| |
| address generate_dsin_dcos(bool isCos) { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = (isCos ? StubGenStubId::dcos_id : StubGenStubId::dsin_id); |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| __ generate_dsin_dcos(isCos, (address)StubRoutines::aarch64::_npio2_hw, |
| (address)StubRoutines::aarch64::_two_over_pi, |
| (address)StubRoutines::aarch64::_pio2, |
| (address)StubRoutines::aarch64::_dsin_coef, |
| (address)StubRoutines::aarch64::_dcos_coef); |
| return start; |
| } |
| |
| // code for comparing 16 characters of strings with Latin1 and Utf16 encoding |
| void compare_string_16_x_LU(Register tmpL, Register tmpU, Label &DIFF1, |
| Label &DIFF2) { |
| Register cnt1 = r2, tmp2 = r11, tmp3 = r12; |
| FloatRegister vtmp = v1, vtmpZ = v0, vtmp3 = v2; |
| |
| __ ldrq(vtmp, Address(__ post(tmp2, 16))); |
| __ ldr(tmpU, Address(__ post(cnt1, 8))); |
| __ zip1(vtmp3, __ T16B, vtmp, vtmpZ); |
| // now we have 32 bytes of characters (converted to U) in vtmp:vtmp3 |
| |
| __ fmovd(tmpL, vtmp3); |
| __ eor(rscratch2, tmp3, tmpL); |
| __ cbnz(rscratch2, DIFF2); |
| |
| __ ldr(tmp3, Address(__ post(cnt1, 8))); |
| __ umov(tmpL, vtmp3, __ D, 1); |
| __ eor(rscratch2, tmpU, tmpL); |
| __ cbnz(rscratch2, DIFF1); |
| |
| __ zip2(vtmp, __ T16B, vtmp, vtmpZ); |
| __ ldr(tmpU, Address(__ post(cnt1, 8))); |
| __ fmovd(tmpL, vtmp); |
| __ eor(rscratch2, tmp3, tmpL); |
| __ cbnz(rscratch2, DIFF2); |
| |
| __ ldr(tmp3, Address(__ post(cnt1, 8))); |
| __ umov(tmpL, vtmp, __ D, 1); |
| __ eor(rscratch2, tmpU, tmpL); |
| __ cbnz(rscratch2, DIFF1); |
| } |
| |
| // r0 = result |
| // r1 = str1 |
| // r2 = cnt1 |
| // r3 = str2 |
| // r4 = cnt2 |
| // r10 = tmp1 |
| // r11 = tmp2 |
| address generate_compare_long_string_different_encoding(bool isLU) { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = (isLU ? StubGenStubId::compare_long_string_LU_id : StubGenStubId::compare_long_string_UL_id); |
| StubCodeMark mark(this, stub_id); |
| address entry = __ pc(); |
| Label SMALL_LOOP, TAIL, TAIL_LOAD_16, LOAD_LAST, DIFF1, DIFF2, |
| DONE, CALCULATE_DIFFERENCE, LARGE_LOOP_PREFETCH, NO_PREFETCH, |
| LARGE_LOOP_PREFETCH_REPEAT1, LARGE_LOOP_PREFETCH_REPEAT2; |
| Register result = r0, str1 = r1, cnt1 = r2, str2 = r3, cnt2 = r4, |
| tmp1 = r10, tmp2 = r11, tmp3 = r12, tmp4 = r14; |
| FloatRegister vtmpZ = v0, vtmp = v1, vtmp3 = v2; |
| RegSet spilled_regs = RegSet::of(tmp3, tmp4); |
| |
| int prefetchLoopExitCondition = MAX2(64, SoftwarePrefetchHintDistance/2); |
| |
| __ eor(vtmpZ, __ T16B, vtmpZ, vtmpZ); |
| // cnt2 == amount of characters left to compare |
| // Check already loaded first 4 symbols(vtmp and tmp2(LU)/tmp1(UL)) |
| __ zip1(vtmp, __ T8B, vtmp, vtmpZ); |
| __ add(str1, str1, isLU ? wordSize/2 : wordSize); |
| __ add(str2, str2, isLU ? wordSize : wordSize/2); |
| __ fmovd(isLU ? tmp1 : tmp2, vtmp); |
| __ subw(cnt2, cnt2, 8); // Already loaded 4 symbols. Last 4 is special case. |
| __ eor(rscratch2, tmp1, tmp2); |
| __ mov(rscratch1, tmp2); |
| __ cbnz(rscratch2, CALCULATE_DIFFERENCE); |
| Register tmpU = isLU ? rscratch1 : tmp1, // where to keep U for comparison |
| tmpL = isLU ? tmp1 : rscratch1; // where to keep L for comparison |
| __ push(spilled_regs, sp); |
| __ mov(tmp2, isLU ? str1 : str2); // init the pointer to L next load |
| __ mov(cnt1, isLU ? str2 : str1); // init the pointer to U next load |
| |
| __ ldr(tmp3, Address(__ post(cnt1, 8))); |
| |
| if (SoftwarePrefetchHintDistance >= 0) { |
| __ subs(rscratch2, cnt2, prefetchLoopExitCondition); |
| __ br(__ LT, NO_PREFETCH); |
| __ bind(LARGE_LOOP_PREFETCH); |
| __ prfm(Address(tmp2, SoftwarePrefetchHintDistance)); |
| __ mov(tmp4, 2); |
| __ prfm(Address(cnt1, SoftwarePrefetchHintDistance)); |
| __ bind(LARGE_LOOP_PREFETCH_REPEAT1); |
| compare_string_16_x_LU(tmpL, tmpU, DIFF1, DIFF2); |
| __ subs(tmp4, tmp4, 1); |
| __ br(__ GT, LARGE_LOOP_PREFETCH_REPEAT1); |
| __ prfm(Address(cnt1, SoftwarePrefetchHintDistance)); |
| __ mov(tmp4, 2); |
| __ bind(LARGE_LOOP_PREFETCH_REPEAT2); |
| compare_string_16_x_LU(tmpL, tmpU, DIFF1, DIFF2); |
| __ subs(tmp4, tmp4, 1); |
| __ br(__ GT, LARGE_LOOP_PREFETCH_REPEAT2); |
| __ sub(cnt2, cnt2, 64); |
| __ subs(rscratch2, cnt2, prefetchLoopExitCondition); |
| __ br(__ GE, LARGE_LOOP_PREFETCH); |
| } |
| __ cbz(cnt2, LOAD_LAST); // no characters left except last load |
| __ bind(NO_PREFETCH); |
| __ subs(cnt2, cnt2, 16); |
| __ br(__ LT, TAIL); |
| __ align(OptoLoopAlignment); |
| __ bind(SMALL_LOOP); // smaller loop |
| __ subs(cnt2, cnt2, 16); |
| compare_string_16_x_LU(tmpL, tmpU, DIFF1, DIFF2); |
| __ br(__ GE, SMALL_LOOP); |
| __ cmn(cnt2, (u1)16); |
| __ br(__ EQ, LOAD_LAST); |
| __ bind(TAIL); // 1..15 characters left until last load (last 4 characters) |
| __ add(cnt1, cnt1, cnt2, __ LSL, 1); // Address of 32 bytes before last 4 characters in UTF-16 string |
| __ add(tmp2, tmp2, cnt2); // Address of 16 bytes before last 4 characters in Latin1 string |
| __ ldr(tmp3, Address(cnt1, -8)); |
| compare_string_16_x_LU(tmpL, tmpU, DIFF1, DIFF2); // last 16 characters before last load |
| __ b(LOAD_LAST); |
| __ bind(DIFF2); |
| __ mov(tmpU, tmp3); |
| __ bind(DIFF1); |
| __ pop(spilled_regs, sp); |
| __ b(CALCULATE_DIFFERENCE); |
| __ bind(LOAD_LAST); |
| // Last 4 UTF-16 characters are already pre-loaded into tmp3 by compare_string_16_x_LU. |
| // No need to load it again |
| __ mov(tmpU, tmp3); |
| __ pop(spilled_regs, sp); |
| |
| // tmp2 points to the address of the last 4 Latin1 characters right now |
| __ ldrs(vtmp, Address(tmp2)); |
| __ zip1(vtmp, __ T8B, vtmp, vtmpZ); |
| __ fmovd(tmpL, vtmp); |
| |
| __ eor(rscratch2, tmpU, tmpL); |
| __ cbz(rscratch2, DONE); |
| |
| // Find the first different characters in the longwords and |
| // compute their difference. |
| __ bind(CALCULATE_DIFFERENCE); |
| __ rev(rscratch2, rscratch2); |
| __ clz(rscratch2, rscratch2); |
| __ andr(rscratch2, rscratch2, -16); |
| __ lsrv(tmp1, tmp1, rscratch2); |
| __ uxthw(tmp1, tmp1); |
| __ lsrv(rscratch1, rscratch1, rscratch2); |
| __ uxthw(rscratch1, rscratch1); |
| __ subw(result, tmp1, rscratch1); |
| __ bind(DONE); |
| __ ret(lr); |
| return entry; |
| } |
| |
| // r0 = input (float16) |
| // v0 = result (float) |
| // v1 = temporary float register |
| address generate_float16ToFloat() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::hf2f_id; |
| StubCodeMark mark(this, stub_id); |
| address entry = __ pc(); |
| BLOCK_COMMENT("Entry:"); |
| __ flt16_to_flt(v0, r0, v1); |
| __ ret(lr); |
| return entry; |
| } |
| |
| // v0 = input (float) |
| // r0 = result (float16) |
| // v1 = temporary float register |
| address generate_floatToFloat16() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::f2hf_id; |
| StubCodeMark mark(this, stub_id); |
| address entry = __ pc(); |
| BLOCK_COMMENT("Entry:"); |
| __ flt_to_flt16(r0, v0, v1); |
| __ ret(lr); |
| return entry; |
| } |
| |
| address generate_method_entry_barrier() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::method_entry_barrier_id; |
| StubCodeMark mark(this, stub_id); |
| |
| Label deoptimize_label; |
| |
| address start = __ pc(); |
| |
| BarrierSetAssembler* bs_asm = BarrierSet::barrier_set()->barrier_set_assembler(); |
| |
| if (bs_asm->nmethod_patching_type() == NMethodPatchingType::conc_instruction_and_data_patch) { |
| BarrierSetNMethod* bs_nm = BarrierSet::barrier_set()->barrier_set_nmethod(); |
| // We can get here despite the nmethod being good, if we have not |
| // yet applied our cross modification fence (or data fence). |
| Address thread_epoch_addr(rthread, in_bytes(bs_nm->thread_disarmed_guard_value_offset()) + 4); |
| __ lea(rscratch2, ExternalAddress(bs_asm->patching_epoch_addr())); |
| __ ldrw(rscratch2, rscratch2); |
| __ strw(rscratch2, thread_epoch_addr); |
| __ isb(); |
| __ membar(__ LoadLoad); |
| } |
| |
| __ set_last_Java_frame(sp, rfp, lr, rscratch1); |
| |
| __ enter(); |
| __ add(rscratch2, sp, wordSize); // rscratch2 points to the saved lr |
| |
| __ sub(sp, sp, 4 * wordSize); // four words for the returned {sp, fp, lr, pc} |
| |
| __ push_call_clobbered_registers(); |
| |
| __ mov(c_rarg0, rscratch2); |
| __ call_VM_leaf |
| (CAST_FROM_FN_PTR |
| (address, BarrierSetNMethod::nmethod_stub_entry_barrier), 1); |
| |
| __ reset_last_Java_frame(true); |
| |
| __ mov(rscratch1, r0); |
| |
| __ pop_call_clobbered_registers(); |
| |
| __ cbnz(rscratch1, deoptimize_label); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| __ BIND(deoptimize_label); |
| |
| __ ldp(/* new sp */ rscratch1, rfp, Address(sp, 0 * wordSize)); |
| __ ldp(lr, /* new pc*/ rscratch2, Address(sp, 2 * wordSize)); |
| |
| __ mov(sp, rscratch1); |
| __ br(rscratch2); |
| |
| return start; |
| } |
| |
| // r0 = result |
| // r1 = str1 |
| // r2 = cnt1 |
| // r3 = str2 |
| // r4 = cnt2 |
| // r10 = tmp1 |
| // r11 = tmp2 |
| address generate_compare_long_string_same_encoding(bool isLL) { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = (isLL ? StubGenStubId::compare_long_string_LL_id : StubGenStubId::compare_long_string_UU_id); |
| StubCodeMark mark(this, stub_id); |
| address entry = __ pc(); |
| Register result = r0, str1 = r1, cnt1 = r2, str2 = r3, cnt2 = r4, |
| tmp1 = r10, tmp2 = r11, tmp1h = rscratch1, tmp2h = rscratch2; |
| |
| Label LARGE_LOOP_PREFETCH, LOOP_COMPARE16, DIFF, LESS16, LESS8, CAL_DIFFERENCE, LENGTH_DIFF; |
| |
| // exit from large loop when less than 64 bytes left to read or we're about |
| // to prefetch memory behind array border |
| int largeLoopExitCondition = MAX2(64, SoftwarePrefetchHintDistance)/(isLL ? 1 : 2); |
| |
| // before jumping to stub, pre-load 8 bytes already, so do comparison directly |
| __ eor(rscratch2, tmp1, tmp2); |
| __ cbnz(rscratch2, CAL_DIFFERENCE); |
| |
| __ sub(cnt2, cnt2, wordSize/(isLL ? 1 : 2)); |
| // update pointers, because of previous read |
| __ add(str1, str1, wordSize); |
| __ add(str2, str2, wordSize); |
| if (SoftwarePrefetchHintDistance >= 0) { |
| __ align(OptoLoopAlignment); |
| __ bind(LARGE_LOOP_PREFETCH); |
| __ prfm(Address(str1, SoftwarePrefetchHintDistance)); |
| __ prfm(Address(str2, SoftwarePrefetchHintDistance)); |
| |
| for (int i = 0; i < 4; i++) { |
| __ ldp(tmp1, tmp1h, Address(str1, i * 16)); |
| __ ldp(tmp2, tmp2h, Address(str2, i * 16)); |
| __ cmp(tmp1, tmp2); |
| __ ccmp(tmp1h, tmp2h, 0, Assembler::EQ); |
| __ br(Assembler::NE, DIFF); |
| } |
| __ sub(cnt2, cnt2, isLL ? 64 : 32); |
| __ add(str1, str1, 64); |
| __ add(str2, str2, 64); |
| __ subs(rscratch2, cnt2, largeLoopExitCondition); |
| __ br(Assembler::GE, LARGE_LOOP_PREFETCH); |
| __ cbz(cnt2, LENGTH_DIFF); // no more chars left? |
| } |
| |
| __ subs(rscratch1, cnt2, isLL ? 16 : 8); |
| __ br(Assembler::LE, LESS16); |
| __ align(OptoLoopAlignment); |
| __ bind(LOOP_COMPARE16); |
| __ ldp(tmp1, tmp1h, Address(__ post(str1, 16))); |
| __ ldp(tmp2, tmp2h, Address(__ post(str2, 16))); |
| __ cmp(tmp1, tmp2); |
| __ ccmp(tmp1h, tmp2h, 0, Assembler::EQ); |
| __ br(Assembler::NE, DIFF); |
| __ sub(cnt2, cnt2, isLL ? 16 : 8); |
| __ subs(rscratch2, cnt2, isLL ? 16 : 8); |
| __ br(Assembler::LT, LESS16); |
| |
| __ ldp(tmp1, tmp1h, Address(__ post(str1, 16))); |
| __ ldp(tmp2, tmp2h, Address(__ post(str2, 16))); |
| __ cmp(tmp1, tmp2); |
| __ ccmp(tmp1h, tmp2h, 0, Assembler::EQ); |
| __ br(Assembler::NE, DIFF); |
| __ sub(cnt2, cnt2, isLL ? 16 : 8); |
| __ subs(rscratch2, cnt2, isLL ? 16 : 8); |
| __ br(Assembler::GE, LOOP_COMPARE16); |
| __ cbz(cnt2, LENGTH_DIFF); |
| |
| __ bind(LESS16); |
| // each 8 compare |
| __ subs(cnt2, cnt2, isLL ? 8 : 4); |
| __ br(Assembler::LE, LESS8); |
| __ ldr(tmp1, Address(__ post(str1, 8))); |
| __ ldr(tmp2, Address(__ post(str2, 8))); |
| __ eor(rscratch2, tmp1, tmp2); |
| __ cbnz(rscratch2, CAL_DIFFERENCE); |
| __ sub(cnt2, cnt2, isLL ? 8 : 4); |
| |
| __ bind(LESS8); // directly load last 8 bytes |
| if (!isLL) { |
| __ add(cnt2, cnt2, cnt2); |
| } |
| __ ldr(tmp1, Address(str1, cnt2)); |
| __ ldr(tmp2, Address(str2, cnt2)); |
| __ eor(rscratch2, tmp1, tmp2); |
| __ cbz(rscratch2, LENGTH_DIFF); |
| __ b(CAL_DIFFERENCE); |
| |
| __ bind(DIFF); |
| __ cmp(tmp1, tmp2); |
| __ csel(tmp1, tmp1, tmp1h, Assembler::NE); |
| __ csel(tmp2, tmp2, tmp2h, Assembler::NE); |
| // reuse rscratch2 register for the result of eor instruction |
| __ eor(rscratch2, tmp1, tmp2); |
| |
| __ bind(CAL_DIFFERENCE); |
| __ rev(rscratch2, rscratch2); |
| __ clz(rscratch2, rscratch2); |
| __ andr(rscratch2, rscratch2, isLL ? -8 : -16); |
| __ lsrv(tmp1, tmp1, rscratch2); |
| __ lsrv(tmp2, tmp2, rscratch2); |
| if (isLL) { |
| __ uxtbw(tmp1, tmp1); |
| __ uxtbw(tmp2, tmp2); |
| } else { |
| __ uxthw(tmp1, tmp1); |
| __ uxthw(tmp2, tmp2); |
| } |
| __ subw(result, tmp1, tmp2); |
| |
| __ bind(LENGTH_DIFF); |
| __ ret(lr); |
| return entry; |
| } |
| |
| enum string_compare_mode { |
| LL, |
| LU, |
| UL, |
| UU, |
| }; |
| |
| // The following registers are declared in aarch64.ad |
| // r0 = result |
| // r1 = str1 |
| // r2 = cnt1 |
| // r3 = str2 |
| // r4 = cnt2 |
| // r10 = tmp1 |
| // r11 = tmp2 |
| // z0 = ztmp1 |
| // z1 = ztmp2 |
| // p0 = pgtmp1 |
| // p1 = pgtmp2 |
| address generate_compare_long_string_sve(string_compare_mode mode) { |
| StubGenStubId stub_id; |
| switch (mode) { |
| case LL: stub_id = StubGenStubId::compare_long_string_LL_id; break; |
| case LU: stub_id = StubGenStubId::compare_long_string_LU_id; break; |
| case UL: stub_id = StubGenStubId::compare_long_string_UL_id; break; |
| case UU: stub_id = StubGenStubId::compare_long_string_UU_id; break; |
| default: ShouldNotReachHere(); |
| } |
| |
| __ align(CodeEntryAlignment); |
| address entry = __ pc(); |
| Register result = r0, str1 = r1, cnt1 = r2, str2 = r3, cnt2 = r4, |
| tmp1 = r10, tmp2 = r11; |
| |
| Label LOOP, DONE, MISMATCH; |
| Register vec_len = tmp1; |
| Register idx = tmp2; |
| // The minimum of the string lengths has been stored in cnt2. |
| Register cnt = cnt2; |
| FloatRegister ztmp1 = z0, ztmp2 = z1; |
| PRegister pgtmp1 = p0, pgtmp2 = p1; |
| |
| #define LOAD_PAIR(ztmp1, ztmp2, pgtmp1, src1, src2, idx) \ |
| switch (mode) { \ |
| case LL: \ |
| __ sve_ld1b(ztmp1, __ B, pgtmp1, Address(str1, idx)); \ |
| __ sve_ld1b(ztmp2, __ B, pgtmp1, Address(str2, idx)); \ |
| break; \ |
| case LU: \ |
| __ sve_ld1b(ztmp1, __ H, pgtmp1, Address(str1, idx)); \ |
| __ sve_ld1h(ztmp2, __ H, pgtmp1, Address(str2, idx, Address::lsl(1))); \ |
| break; \ |
| case UL: \ |
| __ sve_ld1h(ztmp1, __ H, pgtmp1, Address(str1, idx, Address::lsl(1))); \ |
| __ sve_ld1b(ztmp2, __ H, pgtmp1, Address(str2, idx)); \ |
| break; \ |
| case UU: \ |
| __ sve_ld1h(ztmp1, __ H, pgtmp1, Address(str1, idx, Address::lsl(1))); \ |
| __ sve_ld1h(ztmp2, __ H, pgtmp1, Address(str2, idx, Address::lsl(1))); \ |
| break; \ |
| default: \ |
| ShouldNotReachHere(); \ |
| } |
| |
| StubCodeMark mark(this, stub_id); |
| |
| __ mov(idx, 0); |
| __ sve_whilelt(pgtmp1, mode == LL ? __ B : __ H, idx, cnt); |
| |
| if (mode == LL) { |
| __ sve_cntb(vec_len); |
| } else { |
| __ sve_cnth(vec_len); |
| } |
| |
| __ sub(rscratch1, cnt, vec_len); |
| |
| __ bind(LOOP); |
| |
| // main loop |
| LOAD_PAIR(ztmp1, ztmp2, pgtmp1, src1, src2, idx); |
| __ add(idx, idx, vec_len); |
| // Compare strings. |
| __ sve_cmp(Assembler::NE, pgtmp2, mode == LL ? __ B : __ H, pgtmp1, ztmp1, ztmp2); |
| __ br(__ NE, MISMATCH); |
| __ cmp(idx, rscratch1); |
| __ br(__ LT, LOOP); |
| |
| // post loop, last iteration |
| __ sve_whilelt(pgtmp1, mode == LL ? __ B : __ H, idx, cnt); |
| |
| LOAD_PAIR(ztmp1, ztmp2, pgtmp1, src1, src2, idx); |
| __ sve_cmp(Assembler::NE, pgtmp2, mode == LL ? __ B : __ H, pgtmp1, ztmp1, ztmp2); |
| __ br(__ EQ, DONE); |
| |
| __ bind(MISMATCH); |
| |
| // Crop the vector to find its location. |
| __ sve_brkb(pgtmp2, pgtmp1, pgtmp2, false /* isMerge */); |
| // Extract the first different characters of each string. |
| __ sve_lasta(rscratch1, mode == LL ? __ B : __ H, pgtmp2, ztmp1); |
| __ sve_lasta(rscratch2, mode == LL ? __ B : __ H, pgtmp2, ztmp2); |
| |
| // Compute the difference of the first different characters. |
| __ sub(result, rscratch1, rscratch2); |
| |
| __ bind(DONE); |
| __ ret(lr); |
| #undef LOAD_PAIR |
| return entry; |
| } |
| |
| void generate_compare_long_strings() { |
| if (UseSVE == 0) { |
| StubRoutines::aarch64::_compare_long_string_LL |
| = generate_compare_long_string_same_encoding(true); |
| StubRoutines::aarch64::_compare_long_string_UU |
| = generate_compare_long_string_same_encoding(false); |
| StubRoutines::aarch64::_compare_long_string_LU |
| = generate_compare_long_string_different_encoding(true); |
| StubRoutines::aarch64::_compare_long_string_UL |
| = generate_compare_long_string_different_encoding(false); |
| } else { |
| StubRoutines::aarch64::_compare_long_string_LL |
| = generate_compare_long_string_sve(LL); |
| StubRoutines::aarch64::_compare_long_string_UU |
| = generate_compare_long_string_sve(UU); |
| StubRoutines::aarch64::_compare_long_string_LU |
| = generate_compare_long_string_sve(LU); |
| StubRoutines::aarch64::_compare_long_string_UL |
| = generate_compare_long_string_sve(UL); |
| } |
| } |
| |
| // R0 = result |
| // R1 = str2 |
| // R2 = cnt1 |
| // R3 = str1 |
| // R4 = cnt2 |
| // Clobbers: rscratch1, rscratch2, v0, v1, rflags |
| // |
| // This generic linear code use few additional ideas, which makes it faster: |
| // 1) we can safely keep at least 1st register of pattern(since length >= 8) |
| // in order to skip initial loading(help in systems with 1 ld pipeline) |
| // 2) we can use "fast" algorithm of finding single character to search for |
| // first symbol with less branches(1 branch per each loaded register instead |
| // of branch for each symbol), so, this is where constants like |
| // 0x0101...01, 0x00010001...0001, 0x7f7f...7f, 0x7fff7fff...7fff comes from |
| // 3) after loading and analyzing 1st register of source string, it can be |
| // used to search for every 1st character entry, saving few loads in |
| // comparison with "simplier-but-slower" implementation |
| // 4) in order to avoid lots of push/pop operations, code below is heavily |
| // re-using/re-initializing/compressing register values, which makes code |
| // larger and a bit less readable, however, most of extra operations are |
| // issued during loads or branches, so, penalty is minimal |
| address generate_string_indexof_linear(bool str1_isL, bool str2_isL) { |
| StubGenStubId stub_id; |
| if (str1_isL) { |
| if (str2_isL) { |
| stub_id = StubGenStubId::string_indexof_linear_ll_id; |
| } else { |
| stub_id = StubGenStubId::string_indexof_linear_ul_id; |
| } |
| } else { |
| if (str2_isL) { |
| ShouldNotReachHere(); |
| } else { |
| stub_id = StubGenStubId::string_indexof_linear_uu_id; |
| } |
| } |
| __ align(CodeEntryAlignment); |
| StubCodeMark mark(this, stub_id); |
| address entry = __ pc(); |
| |
| int str1_chr_size = str1_isL ? 1 : 2; |
| int str2_chr_size = str2_isL ? 1 : 2; |
| int str1_chr_shift = str1_isL ? 0 : 1; |
| int str2_chr_shift = str2_isL ? 0 : 1; |
| bool isL = str1_isL && str2_isL; |
| // parameters |
| Register result = r0, str2 = r1, cnt1 = r2, str1 = r3, cnt2 = r4; |
| // temporary registers |
| Register tmp1 = r20, tmp2 = r21, tmp3 = r22, tmp4 = r23; |
| RegSet spilled_regs = RegSet::range(tmp1, tmp4); |
| // redefinitions |
| Register ch1 = rscratch1, ch2 = rscratch2, first = tmp3; |
| |
| __ push(spilled_regs, sp); |
| Label L_LOOP, L_LOOP_PROCEED, L_SMALL, L_HAS_ZERO, |
| L_HAS_ZERO_LOOP, L_CMP_LOOP, L_CMP_LOOP_NOMATCH, L_SMALL_PROCEED, |
| L_SMALL_HAS_ZERO_LOOP, L_SMALL_CMP_LOOP_NOMATCH, L_SMALL_CMP_LOOP, |
| L_POST_LOOP, L_CMP_LOOP_LAST_CMP, L_HAS_ZERO_LOOP_NOMATCH, |
| L_SMALL_CMP_LOOP_LAST_CMP, L_SMALL_CMP_LOOP_LAST_CMP2, |
| L_CMP_LOOP_LAST_CMP2, DONE, NOMATCH; |
| // Read whole register from str1. It is safe, because length >=8 here |
| __ ldr(ch1, Address(str1)); |
| // Read whole register from str2. It is safe, because length >=8 here |
| __ ldr(ch2, Address(str2)); |
| __ sub(cnt2, cnt2, cnt1); |
| __ andr(first, ch1, str1_isL ? 0xFF : 0xFFFF); |
| if (str1_isL != str2_isL) { |
| __ eor(v0, __ T16B, v0, v0); |
| } |
| __ mov(tmp1, str2_isL ? 0x0101010101010101 : 0x0001000100010001); |
| __ mul(first, first, tmp1); |
| // check if we have less than 1 register to check |
| __ subs(cnt2, cnt2, wordSize/str2_chr_size - 1); |
| if (str1_isL != str2_isL) { |
| __ fmovd(v1, ch1); |
| } |
| __ br(__ LE, L_SMALL); |
| __ eor(ch2, first, ch2); |
| if (str1_isL != str2_isL) { |
| __ zip1(v1, __ T16B, v1, v0); |
| } |
| __ sub(tmp2, ch2, tmp1); |
| __ orr(ch2, ch2, str2_isL ? 0x7f7f7f7f7f7f7f7f : 0x7fff7fff7fff7fff); |
| __ bics(tmp2, tmp2, ch2); |
| if (str1_isL != str2_isL) { |
| __ fmovd(ch1, v1); |
| } |
| __ br(__ NE, L_HAS_ZERO); |
| __ subs(cnt2, cnt2, wordSize/str2_chr_size); |
| __ add(result, result, wordSize/str2_chr_size); |
| __ add(str2, str2, wordSize); |
| __ br(__ LT, L_POST_LOOP); |
| __ BIND(L_LOOP); |
| __ ldr(ch2, Address(str2)); |
| __ eor(ch2, first, ch2); |
| __ sub(tmp2, ch2, tmp1); |
| __ orr(ch2, ch2, str2_isL ? 0x7f7f7f7f7f7f7f7f : 0x7fff7fff7fff7fff); |
| __ bics(tmp2, tmp2, ch2); |
| __ br(__ NE, L_HAS_ZERO); |
| __ BIND(L_LOOP_PROCEED); |
| __ subs(cnt2, cnt2, wordSize/str2_chr_size); |
| __ add(str2, str2, wordSize); |
| __ add(result, result, wordSize/str2_chr_size); |
| __ br(__ GE, L_LOOP); |
| __ BIND(L_POST_LOOP); |
| __ subs(zr, cnt2, -wordSize/str2_chr_size); // no extra characters to check |
| __ br(__ LE, NOMATCH); |
| __ ldr(ch2, Address(str2)); |
| __ sub(cnt2, zr, cnt2, __ LSL, LogBitsPerByte + str2_chr_shift); |
| __ eor(ch2, first, ch2); |
| __ sub(tmp2, ch2, tmp1); |
| __ orr(ch2, ch2, str2_isL ? 0x7f7f7f7f7f7f7f7f : 0x7fff7fff7fff7fff); |
| __ mov(tmp4, -1); // all bits set |
| __ b(L_SMALL_PROCEED); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_SMALL); |
| __ sub(cnt2, zr, cnt2, __ LSL, LogBitsPerByte + str2_chr_shift); |
| __ eor(ch2, first, ch2); |
| if (str1_isL != str2_isL) { |
| __ zip1(v1, __ T16B, v1, v0); |
| } |
| __ sub(tmp2, ch2, tmp1); |
| __ mov(tmp4, -1); // all bits set |
| __ orr(ch2, ch2, str2_isL ? 0x7f7f7f7f7f7f7f7f : 0x7fff7fff7fff7fff); |
| if (str1_isL != str2_isL) { |
| __ fmovd(ch1, v1); // move converted 4 symbols |
| } |
| __ BIND(L_SMALL_PROCEED); |
| __ lsrv(tmp4, tmp4, cnt2); // mask. zeroes on useless bits. |
| __ bic(tmp2, tmp2, ch2); |
| __ ands(tmp2, tmp2, tmp4); // clear useless bits and check |
| __ rbit(tmp2, tmp2); |
| __ br(__ EQ, NOMATCH); |
| __ BIND(L_SMALL_HAS_ZERO_LOOP); |
| __ clz(tmp4, tmp2); // potentially long. Up to 4 cycles on some cpu's |
| __ cmp(cnt1, u1(wordSize/str2_chr_size)); |
| __ br(__ LE, L_SMALL_CMP_LOOP_LAST_CMP2); |
| if (str2_isL) { // LL |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte); // address of "index" |
| __ ldr(ch2, Address(str2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); // shift off leading zeroes from match info |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte); |
| __ lsl(tmp2, tmp2, 1); // shift off leading "1" from match info |
| } else { |
| __ mov(ch2, 0xE); // all bits in byte set except last one |
| __ andr(ch2, ch2, tmp4, __ LSR, LogBitsPerByte); // byte shift amount |
| __ ldr(ch2, Address(str2, ch2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ lsl(tmp2, tmp2, 1); // shift off leading "1" from match info |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| } |
| __ cmp(ch1, ch2); |
| __ mov(tmp4, wordSize/str2_chr_size); |
| __ br(__ NE, L_SMALL_CMP_LOOP_NOMATCH); |
| __ BIND(L_SMALL_CMP_LOOP); |
| str1_isL ? __ ldrb(first, Address(str1, tmp4, Address::lsl(str1_chr_shift))) |
| : __ ldrh(first, Address(str1, tmp4, Address::lsl(str1_chr_shift))); |
| str2_isL ? __ ldrb(ch2, Address(str2, tmp4, Address::lsl(str2_chr_shift))) |
| : __ ldrh(ch2, Address(str2, tmp4, Address::lsl(str2_chr_shift))); |
| __ add(tmp4, tmp4, 1); |
| __ cmp(tmp4, cnt1); |
| __ br(__ GE, L_SMALL_CMP_LOOP_LAST_CMP); |
| __ cmp(first, ch2); |
| __ br(__ EQ, L_SMALL_CMP_LOOP); |
| __ BIND(L_SMALL_CMP_LOOP_NOMATCH); |
| __ cbz(tmp2, NOMATCH); // no more matches. exit |
| __ clz(tmp4, tmp2); |
| __ add(result, result, 1); // advance index |
| __ add(str2, str2, str2_chr_size); // advance pointer |
| __ b(L_SMALL_HAS_ZERO_LOOP); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_SMALL_CMP_LOOP_LAST_CMP); |
| __ cmp(first, ch2); |
| __ br(__ NE, L_SMALL_CMP_LOOP_NOMATCH); |
| __ b(DONE); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_SMALL_CMP_LOOP_LAST_CMP2); |
| if (str2_isL) { // LL |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte); // address of "index" |
| __ ldr(ch2, Address(str2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); // shift off leading zeroes from match info |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte); |
| __ lsl(tmp2, tmp2, 1); // shift off leading "1" from match info |
| } else { |
| __ mov(ch2, 0xE); // all bits in byte set except last one |
| __ andr(ch2, ch2, tmp4, __ LSR, LogBitsPerByte); // byte shift amount |
| __ ldr(ch2, Address(str2, ch2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ lsl(tmp2, tmp2, 1); // shift off leading "1" from match info |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| } |
| __ cmp(ch1, ch2); |
| __ br(__ NE, L_SMALL_CMP_LOOP_NOMATCH); |
| __ b(DONE); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_HAS_ZERO); |
| __ rbit(tmp2, tmp2); |
| __ clz(tmp4, tmp2); // potentially long. Up to 4 cycles on some CPU's |
| // Now, perform compression of counters(cnt2 and cnt1) into one register. |
| // It's fine because both counters are 32bit and are not changed in this |
| // loop. Just restore it on exit. So, cnt1 can be re-used in this loop. |
| __ orr(cnt2, cnt2, cnt1, __ LSL, BitsPerByte * wordSize / 2); |
| __ sub(result, result, 1); |
| __ BIND(L_HAS_ZERO_LOOP); |
| __ mov(cnt1, wordSize/str2_chr_size); |
| __ cmp(cnt1, cnt2, __ LSR, BitsPerByte * wordSize / 2); |
| __ br(__ GE, L_CMP_LOOP_LAST_CMP2); // case of 8 bytes only to compare |
| if (str2_isL) { |
| __ lsr(ch2, tmp4, LogBitsPerByte + str2_chr_shift); // char index |
| __ ldr(ch2, Address(str2, ch2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ add(tmp4, tmp4, 1); |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ lsl(tmp2, tmp2, 1); |
| __ mov(tmp4, wordSize/str2_chr_size); |
| } else { |
| __ mov(ch2, 0xE); |
| __ andr(ch2, ch2, tmp4, __ LSR, LogBitsPerByte); // byte shift amount |
| __ ldr(ch2, Address(str2, ch2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); |
| __ add(tmp4, tmp4, 1); |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte); |
| __ lsl(tmp2, tmp2, 1); |
| __ mov(tmp4, wordSize/str2_chr_size); |
| __ sub(str2, str2, str2_chr_size); |
| } |
| __ cmp(ch1, ch2); |
| __ mov(tmp4, wordSize/str2_chr_size); |
| __ br(__ NE, L_CMP_LOOP_NOMATCH); |
| __ BIND(L_CMP_LOOP); |
| str1_isL ? __ ldrb(cnt1, Address(str1, tmp4, Address::lsl(str1_chr_shift))) |
| : __ ldrh(cnt1, Address(str1, tmp4, Address::lsl(str1_chr_shift))); |
| str2_isL ? __ ldrb(ch2, Address(str2, tmp4, Address::lsl(str2_chr_shift))) |
| : __ ldrh(ch2, Address(str2, tmp4, Address::lsl(str2_chr_shift))); |
| __ add(tmp4, tmp4, 1); |
| __ cmp(tmp4, cnt2, __ LSR, BitsPerByte * wordSize / 2); |
| __ br(__ GE, L_CMP_LOOP_LAST_CMP); |
| __ cmp(cnt1, ch2); |
| __ br(__ EQ, L_CMP_LOOP); |
| __ BIND(L_CMP_LOOP_NOMATCH); |
| // here we're not matched |
| __ cbz(tmp2, L_HAS_ZERO_LOOP_NOMATCH); // no more matches. Proceed to main loop |
| __ clz(tmp4, tmp2); |
| __ add(str2, str2, str2_chr_size); // advance pointer |
| __ b(L_HAS_ZERO_LOOP); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_CMP_LOOP_LAST_CMP); |
| __ cmp(cnt1, ch2); |
| __ br(__ NE, L_CMP_LOOP_NOMATCH); |
| __ b(DONE); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_CMP_LOOP_LAST_CMP2); |
| if (str2_isL) { |
| __ lsr(ch2, tmp4, LogBitsPerByte + str2_chr_shift); // char index |
| __ ldr(ch2, Address(str2, ch2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ add(tmp4, tmp4, 1); |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ lsl(tmp2, tmp2, 1); |
| } else { |
| __ mov(ch2, 0xE); |
| __ andr(ch2, ch2, tmp4, __ LSR, LogBitsPerByte); // byte shift amount |
| __ ldr(ch2, Address(str2, ch2)); // read whole register of str2. Safe. |
| __ lslv(tmp2, tmp2, tmp4); |
| __ add(tmp4, tmp4, 1); |
| __ add(result, result, tmp4, __ LSR, LogBitsPerByte + str2_chr_shift); |
| __ add(str2, str2, tmp4, __ LSR, LogBitsPerByte); |
| __ lsl(tmp2, tmp2, 1); |
| __ sub(str2, str2, str2_chr_size); |
| } |
| __ cmp(ch1, ch2); |
| __ br(__ NE, L_CMP_LOOP_NOMATCH); |
| __ b(DONE); |
| __ align(OptoLoopAlignment); |
| __ BIND(L_HAS_ZERO_LOOP_NOMATCH); |
| // 1) Restore "result" index. Index was wordSize/str2_chr_size * N until |
| // L_HAS_ZERO block. Byte octet was analyzed in L_HAS_ZERO_LOOP, |
| // so, result was increased at max by wordSize/str2_chr_size - 1, so, |
| // respective high bit wasn't changed. L_LOOP_PROCEED will increase |
| // result by analyzed characters value, so, we can just reset lower bits |
| // in result here. Clear 2 lower bits for UU/UL and 3 bits for LL |
| // 2) restore cnt1 and cnt2 values from "compressed" cnt2 |
| // 3) advance str2 value to represent next str2 octet. result & 7/3 is |
| // index of last analyzed substring inside current octet. So, str2 in at |
| // respective start address. We need to advance it to next octet |
| __ andr(tmp2, result, wordSize/str2_chr_size - 1); // symbols analyzed |
| __ lsr(cnt1, cnt2, BitsPerByte * wordSize / 2); |
| __ bfm(result, zr, 0, 2 - str2_chr_shift); |
| __ sub(str2, str2, tmp2, __ LSL, str2_chr_shift); // restore str2 |
| __ movw(cnt2, cnt2); |
| __ b(L_LOOP_PROCEED); |
| __ align(OptoLoopAlignment); |
| __ BIND(NOMATCH); |
| __ mov(result, -1); |
| __ BIND(DONE); |
| __ pop(spilled_regs, sp); |
| __ ret(lr); |
| return entry; |
| } |
| |
| void generate_string_indexof_stubs() { |
| StubRoutines::aarch64::_string_indexof_linear_ll = generate_string_indexof_linear(true, true); |
| StubRoutines::aarch64::_string_indexof_linear_uu = generate_string_indexof_linear(false, false); |
| StubRoutines::aarch64::_string_indexof_linear_ul = generate_string_indexof_linear(true, false); |
| } |
| |
| void inflate_and_store_2_fp_registers(bool generatePrfm, |
| FloatRegister src1, FloatRegister src2) { |
| Register dst = r1; |
| __ zip1(v1, __ T16B, src1, v0); |
| __ zip2(v2, __ T16B, src1, v0); |
| if (generatePrfm) { |
| __ prfm(Address(dst, SoftwarePrefetchHintDistance), PSTL1STRM); |
| } |
| __ zip1(v3, __ T16B, src2, v0); |
| __ zip2(v4, __ T16B, src2, v0); |
| __ st1(v1, v2, v3, v4, __ T16B, Address(__ post(dst, 64))); |
| } |
| |
| // R0 = src |
| // R1 = dst |
| // R2 = len |
| // R3 = len >> 3 |
| // V0 = 0 |
| // v1 = loaded 8 bytes |
| // Clobbers: r0, r1, r3, rscratch1, rflags, v0-v6 |
| address generate_large_byte_array_inflate() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::large_byte_array_inflate_id; |
| StubCodeMark mark(this, stub_id); |
| address entry = __ pc(); |
| Label LOOP, LOOP_START, LOOP_PRFM, LOOP_PRFM_START, DONE; |
| Register src = r0, dst = r1, len = r2, octetCounter = r3; |
| const int large_loop_threshold = MAX2(64, SoftwarePrefetchHintDistance)/8 + 4; |
| |
| // do one more 8-byte read to have address 16-byte aligned in most cases |
| // also use single store instruction |
| __ ldrd(v2, __ post(src, 8)); |
| __ sub(octetCounter, octetCounter, 2); |
| __ zip1(v1, __ T16B, v1, v0); |
| __ zip1(v2, __ T16B, v2, v0); |
| __ st1(v1, v2, __ T16B, __ post(dst, 32)); |
| __ ld1(v3, v4, v5, v6, __ T16B, Address(__ post(src, 64))); |
| __ subs(rscratch1, octetCounter, large_loop_threshold); |
| __ br(__ LE, LOOP_START); |
| __ b(LOOP_PRFM_START); |
| __ bind(LOOP_PRFM); |
| __ ld1(v3, v4, v5, v6, __ T16B, Address(__ post(src, 64))); |
| __ bind(LOOP_PRFM_START); |
| __ prfm(Address(src, SoftwarePrefetchHintDistance)); |
| __ sub(octetCounter, octetCounter, 8); |
| __ subs(rscratch1, octetCounter, large_loop_threshold); |
| inflate_and_store_2_fp_registers(true, v3, v4); |
| inflate_and_store_2_fp_registers(true, v5, v6); |
| __ br(__ GT, LOOP_PRFM); |
| __ cmp(octetCounter, (u1)8); |
| __ br(__ LT, DONE); |
| __ bind(LOOP); |
| __ ld1(v3, v4, v5, v6, __ T16B, Address(__ post(src, 64))); |
| __ bind(LOOP_START); |
| __ sub(octetCounter, octetCounter, 8); |
| __ cmp(octetCounter, (u1)8); |
| inflate_and_store_2_fp_registers(false, v3, v4); |
| inflate_and_store_2_fp_registers(false, v5, v6); |
| __ br(__ GE, LOOP); |
| __ bind(DONE); |
| __ ret(lr); |
| return entry; |
| } |
| |
| /** |
| * Arguments: |
| * |
| * Input: |
| * c_rarg0 - current state address |
| * c_rarg1 - H key address |
| * c_rarg2 - data address |
| * c_rarg3 - number of blocks |
| * |
| * Output: |
| * Updated state at c_rarg0 |
| */ |
| address generate_ghash_processBlocks() { |
| // Bafflingly, GCM uses little-endian for the byte order, but |
| // big-endian for the bit order. For example, the polynomial 1 is |
| // represented as the 16-byte string 80 00 00 00 | 12 bytes of 00. |
| // |
| // So, we must either reverse the bytes in each word and do |
| // everything big-endian or reverse the bits in each byte and do |
| // it little-endian. On AArch64 it's more idiomatic to reverse |
| // the bits in each byte (we have an instruction, RBIT, to do |
| // that) and keep the data in little-endian bit order through the |
| // calculation, bit-reversing the inputs and outputs. |
| |
| StubGenStubId stub_id = StubGenStubId::ghash_processBlocks_id; |
| StubCodeMark mark(this, stub_id); |
| __ align(wordSize * 2); |
| address p = __ pc(); |
| __ emit_int64(0x87); // The low-order bits of the field |
| // polynomial (i.e. p = z^7+z^2+z+1) |
| // repeated in the low and high parts of a |
| // 128-bit vector |
| __ emit_int64(0x87); |
| |
| __ align(CodeEntryAlignment); |
| address start = __ pc(); |
| |
| Register state = c_rarg0; |
| Register subkeyH = c_rarg1; |
| Register data = c_rarg2; |
| Register blocks = c_rarg3; |
| |
| FloatRegister vzr = v30; |
| __ eor(vzr, __ T16B, vzr, vzr); // zero register |
| |
| __ ldrq(v24, p); // The field polynomial |
| |
| __ ldrq(v0, Address(state)); |
| __ ldrq(v1, Address(subkeyH)); |
| |
| __ rev64(v0, __ T16B, v0); // Bit-reverse words in state and subkeyH |
| __ rbit(v0, __ T16B, v0); |
| __ rev64(v1, __ T16B, v1); |
| __ rbit(v1, __ T16B, v1); |
| |
| __ ext(v4, __ T16B, v1, v1, 0x08); // long-swap subkeyH into v1 |
| __ eor(v4, __ T16B, v4, v1); // xor subkeyH into subkeyL (Karatsuba: (A1+A0)) |
| |
| { |
| Label L_ghash_loop; |
| __ bind(L_ghash_loop); |
| |
| __ ldrq(v2, Address(__ post(data, 0x10))); // Load the data, bit |
| // reversing each byte |
| __ rbit(v2, __ T16B, v2); |
| __ eor(v2, __ T16B, v0, v2); // bit-swapped data ^ bit-swapped state |
| |
| // Multiply state in v2 by subkey in v1 |
| __ ghash_multiply(/*result_lo*/v5, /*result_hi*/v7, |
| /*a*/v1, /*b*/v2, /*a1_xor_a0*/v4, |
| /*temps*/v6, v3, /*reuse/clobber b*/v2); |
| // Reduce v7:v5 by the field polynomial |
| __ ghash_reduce(/*result*/v0, /*lo*/v5, /*hi*/v7, /*p*/v24, vzr, /*temp*/v3); |
| |
| __ sub(blocks, blocks, 1); |
| __ cbnz(blocks, L_ghash_loop); |
| } |
| |
| // The bit-reversed result is at this point in v0 |
| __ rev64(v0, __ T16B, v0); |
| __ rbit(v0, __ T16B, v0); |
| |
| __ st1(v0, __ T16B, state); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| address generate_ghash_processBlocks_wide() { |
| address small = generate_ghash_processBlocks(); |
| |
| StubGenStubId stub_id = StubGenStubId::ghash_processBlocks_wide_id; |
| StubCodeMark mark(this, stub_id); |
| __ align(wordSize * 2); |
| address p = __ pc(); |
| __ emit_int64(0x87); // The low-order bits of the field |
| // polynomial (i.e. p = z^7+z^2+z+1) |
| // repeated in the low and high parts of a |
| // 128-bit vector |
| __ emit_int64(0x87); |
| |
| __ align(CodeEntryAlignment); |
| address start = __ pc(); |
| |
| Register state = c_rarg0; |
| Register subkeyH = c_rarg1; |
| Register data = c_rarg2; |
| Register blocks = c_rarg3; |
| |
| const int unroll = 4; |
| |
| __ cmp(blocks, (unsigned char)(unroll * 2)); |
| __ br(__ LT, small); |
| |
| if (unroll > 1) { |
| // Save state before entering routine |
| __ sub(sp, sp, 4 * 16); |
| __ st1(v12, v13, v14, v15, __ T16B, Address(sp)); |
| __ sub(sp, sp, 4 * 16); |
| __ st1(v8, v9, v10, v11, __ T16B, Address(sp)); |
| } |
| |
| __ ghash_processBlocks_wide(p, state, subkeyH, data, blocks, unroll); |
| |
| if (unroll > 1) { |
| // And restore state |
| __ ld1(v8, v9, v10, v11, __ T16B, __ post(sp, 4 * 16)); |
| __ ld1(v12, v13, v14, v15, __ T16B, __ post(sp, 4 * 16)); |
| } |
| |
| __ cmp(blocks, (unsigned char)0); |
| __ br(__ GT, small); |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| void generate_base64_encode_simdround(Register src, Register dst, |
| FloatRegister codec, u8 size) { |
| |
| FloatRegister in0 = v4, in1 = v5, in2 = v6; |
| FloatRegister out0 = v16, out1 = v17, out2 = v18, out3 = v19; |
| FloatRegister ind0 = v20, ind1 = v21, ind2 = v22, ind3 = v23; |
| |
| Assembler::SIMD_Arrangement arrangement = size == 16 ? __ T16B : __ T8B; |
| |
| __ ld3(in0, in1, in2, arrangement, __ post(src, 3 * size)); |
| |
| __ ushr(ind0, arrangement, in0, 2); |
| |
| __ ushr(ind1, arrangement, in1, 2); |
| __ shl(in0, arrangement, in0, 6); |
| __ orr(ind1, arrangement, ind1, in0); |
| __ ushr(ind1, arrangement, ind1, 2); |
| |
| __ ushr(ind2, arrangement, in2, 4); |
| __ shl(in1, arrangement, in1, 4); |
| __ orr(ind2, arrangement, in1, ind2); |
| __ ushr(ind2, arrangement, ind2, 2); |
| |
| __ shl(ind3, arrangement, in2, 2); |
| __ ushr(ind3, arrangement, ind3, 2); |
| |
| __ tbl(out0, arrangement, codec, 4, ind0); |
| __ tbl(out1, arrangement, codec, 4, ind1); |
| __ tbl(out2, arrangement, codec, 4, ind2); |
| __ tbl(out3, arrangement, codec, 4, ind3); |
| |
| __ st4(out0, out1, out2, out3, arrangement, __ post(dst, 4 * size)); |
| } |
| |
| /** |
| * Arguments: |
| * |
| * Input: |
| * c_rarg0 - src_start |
| * c_rarg1 - src_offset |
| * c_rarg2 - src_length |
| * c_rarg3 - dest_start |
| * c_rarg4 - dest_offset |
| * c_rarg5 - isURL |
| * |
| */ |
| address generate_base64_encodeBlock() { |
| |
| static const char toBase64[64] = { |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', |
| 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', |
| 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', |
| 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' |
| }; |
| |
| static const char toBase64URL[64] = { |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', |
| 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', |
| 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', |
| 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' |
| }; |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::base64_encodeBlock_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register src = c_rarg0; // source array |
| Register soff = c_rarg1; // source start offset |
| Register send = c_rarg2; // source end offset |
| Register dst = c_rarg3; // dest array |
| Register doff = c_rarg4; // position for writing to dest array |
| Register isURL = c_rarg5; // Base64 or URL character set |
| |
| // c_rarg6 and c_rarg7 are free to use as temps |
| Register codec = c_rarg6; |
| Register length = c_rarg7; |
| |
| Label ProcessData, Process48B, Process24B, Process3B, SIMDExit, Exit; |
| |
| __ add(src, src, soff); |
| __ add(dst, dst, doff); |
| __ sub(length, send, soff); |
| |
| // load the codec base address |
| __ lea(codec, ExternalAddress((address) toBase64)); |
| __ cbz(isURL, ProcessData); |
| __ lea(codec, ExternalAddress((address) toBase64URL)); |
| |
| __ BIND(ProcessData); |
| |
| // too short to formup a SIMD loop, roll back |
| __ cmp(length, (u1)24); |
| __ br(Assembler::LT, Process3B); |
| |
| __ ld1(v0, v1, v2, v3, __ T16B, Address(codec)); |
| |
| __ BIND(Process48B); |
| __ cmp(length, (u1)48); |
| __ br(Assembler::LT, Process24B); |
| generate_base64_encode_simdround(src, dst, v0, 16); |
| __ sub(length, length, 48); |
| __ b(Process48B); |
| |
| __ BIND(Process24B); |
| __ cmp(length, (u1)24); |
| __ br(Assembler::LT, SIMDExit); |
| generate_base64_encode_simdround(src, dst, v0, 8); |
| __ sub(length, length, 24); |
| |
| __ BIND(SIMDExit); |
| __ cbz(length, Exit); |
| |
| __ BIND(Process3B); |
| // 3 src bytes, 24 bits |
| __ ldrb(r10, __ post(src, 1)); |
| __ ldrb(r11, __ post(src, 1)); |
| __ ldrb(r12, __ post(src, 1)); |
| __ orrw(r11, r11, r10, Assembler::LSL, 8); |
| __ orrw(r12, r12, r11, Assembler::LSL, 8); |
| // codec index |
| __ ubfmw(r15, r12, 18, 23); |
| __ ubfmw(r14, r12, 12, 17); |
| __ ubfmw(r13, r12, 6, 11); |
| __ andw(r12, r12, 63); |
| // get the code based on the codec |
| __ ldrb(r15, Address(codec, r15, Address::uxtw(0))); |
| __ ldrb(r14, Address(codec, r14, Address::uxtw(0))); |
| __ ldrb(r13, Address(codec, r13, Address::uxtw(0))); |
| __ ldrb(r12, Address(codec, r12, Address::uxtw(0))); |
| __ strb(r15, __ post(dst, 1)); |
| __ strb(r14, __ post(dst, 1)); |
| __ strb(r13, __ post(dst, 1)); |
| __ strb(r12, __ post(dst, 1)); |
| __ sub(length, length, 3); |
| __ cbnz(length, Process3B); |
| |
| __ BIND(Exit); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| void generate_base64_decode_simdround(Register src, Register dst, |
| FloatRegister codecL, FloatRegister codecH, int size, Label& Exit) { |
| |
| FloatRegister in0 = v16, in1 = v17, in2 = v18, in3 = v19; |
| FloatRegister out0 = v20, out1 = v21, out2 = v22; |
| |
| FloatRegister decL0 = v23, decL1 = v24, decL2 = v25, decL3 = v26; |
| FloatRegister decH0 = v28, decH1 = v29, decH2 = v30, decH3 = v31; |
| |
| Label NoIllegalData, ErrorInLowerHalf, StoreLegalData; |
| |
| Assembler::SIMD_Arrangement arrangement = size == 16 ? __ T16B : __ T8B; |
| |
| __ ld4(in0, in1, in2, in3, arrangement, __ post(src, 4 * size)); |
| |
| // we need unsigned saturating subtract, to make sure all input values |
| // in range [0, 63] will have 0U value in the higher half lookup |
| __ uqsubv(decH0, __ T16B, in0, v27); |
| __ uqsubv(decH1, __ T16B, in1, v27); |
| __ uqsubv(decH2, __ T16B, in2, v27); |
| __ uqsubv(decH3, __ T16B, in3, v27); |
| |
| // lower half lookup |
| __ tbl(decL0, arrangement, codecL, 4, in0); |
| __ tbl(decL1, arrangement, codecL, 4, in1); |
| __ tbl(decL2, arrangement, codecL, 4, in2); |
| __ tbl(decL3, arrangement, codecL, 4, in3); |
| |
| // higher half lookup |
| __ tbx(decH0, arrangement, codecH, 4, decH0); |
| __ tbx(decH1, arrangement, codecH, 4, decH1); |
| __ tbx(decH2, arrangement, codecH, 4, decH2); |
| __ tbx(decH3, arrangement, codecH, 4, decH3); |
| |
| // combine lower and higher |
| __ orr(decL0, arrangement, decL0, decH0); |
| __ orr(decL1, arrangement, decL1, decH1); |
| __ orr(decL2, arrangement, decL2, decH2); |
| __ orr(decL3, arrangement, decL3, decH3); |
| |
| // check illegal inputs, value larger than 63 (maximum of 6 bits) |
| __ cm(Assembler::HI, decH0, arrangement, decL0, v27); |
| __ cm(Assembler::HI, decH1, arrangement, decL1, v27); |
| __ cm(Assembler::HI, decH2, arrangement, decL2, v27); |
| __ cm(Assembler::HI, decH3, arrangement, decL3, v27); |
| __ orr(in0, arrangement, decH0, decH1); |
| __ orr(in1, arrangement, decH2, decH3); |
| __ orr(in2, arrangement, in0, in1); |
| __ umaxv(in3, arrangement, in2); |
| __ umov(rscratch2, in3, __ B, 0); |
| |
| // get the data to output |
| __ shl(out0, arrangement, decL0, 2); |
| __ ushr(out1, arrangement, decL1, 4); |
| __ orr(out0, arrangement, out0, out1); |
| __ shl(out1, arrangement, decL1, 4); |
| __ ushr(out2, arrangement, decL2, 2); |
| __ orr(out1, arrangement, out1, out2); |
| __ shl(out2, arrangement, decL2, 6); |
| __ orr(out2, arrangement, out2, decL3); |
| |
| __ cbz(rscratch2, NoIllegalData); |
| |
| // handle illegal input |
| __ umov(r10, in2, __ D, 0); |
| if (size == 16) { |
| __ cbnz(r10, ErrorInLowerHalf); |
| |
| // illegal input is in higher half, store the lower half now. |
| __ st3(out0, out1, out2, __ T8B, __ post(dst, 24)); |
| |
| __ umov(r10, in2, __ D, 1); |
| __ umov(r11, out0, __ D, 1); |
| __ umov(r12, out1, __ D, 1); |
| __ umov(r13, out2, __ D, 1); |
| __ b(StoreLegalData); |
| |
| __ BIND(ErrorInLowerHalf); |
| } |
| __ umov(r11, out0, __ D, 0); |
| __ umov(r12, out1, __ D, 0); |
| __ umov(r13, out2, __ D, 0); |
| |
| __ BIND(StoreLegalData); |
| __ tbnz(r10, 5, Exit); // 0xff indicates illegal input |
| __ strb(r11, __ post(dst, 1)); |
| __ strb(r12, __ post(dst, 1)); |
| __ strb(r13, __ post(dst, 1)); |
| __ lsr(r10, r10, 8); |
| __ lsr(r11, r11, 8); |
| __ lsr(r12, r12, 8); |
| __ lsr(r13, r13, 8); |
| __ b(StoreLegalData); |
| |
| __ BIND(NoIllegalData); |
| __ st3(out0, out1, out2, arrangement, __ post(dst, 3 * size)); |
| } |
| |
| |
| /** |
| * Arguments: |
| * |
| * Input: |
| * c_rarg0 - src_start |
| * c_rarg1 - src_offset |
| * c_rarg2 - src_length |
| * c_rarg3 - dest_start |
| * c_rarg4 - dest_offset |
| * c_rarg5 - isURL |
| * c_rarg6 - isMIME |
| * |
| */ |
| address generate_base64_decodeBlock() { |
| |
| // The SIMD part of this Base64 decode intrinsic is based on the algorithm outlined |
| // on http://0x80.pl/articles/base64-simd-neon.html#encoding-quadwords, in section |
| // titled "Base64 decoding". |
| |
| // Non-SIMD lookup tables are mostly dumped from fromBase64 array used in java.util.Base64, |
| // except the trailing character '=' is also treated illegal value in this intrinsic. That |
| // is java.util.Base64.fromBase64['='] = -2, while fromBase(URL)64ForNoSIMD['='] = 255 here. |
| static const uint8_t fromBase64ForNoSIMD[256] = { |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 62u, 255u, 255u, 255u, 63u, |
| 52u, 53u, 54u, 55u, 56u, 57u, 58u, 59u, 60u, 61u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, |
| 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, 40u, |
| 41u, 42u, 43u, 44u, 45u, 46u, 47u, 48u, 49u, 50u, 51u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| }; |
| |
| static const uint8_t fromBase64URLForNoSIMD[256] = { |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 62u, 255u, 255u, |
| 52u, 53u, 54u, 55u, 56u, 57u, 58u, 59u, 60u, 61u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, |
| 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 255u, 255u, 255u, 255u, 63u, |
| 255u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, 40u, |
| 41u, 42u, 43u, 44u, 45u, 46u, 47u, 48u, 49u, 50u, 51u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| }; |
| |
| // A legal value of base64 code is in range [0, 127]. We need two lookups |
| // with tbl/tbx and combine them to get the decode data. The 1st table vector |
| // lookup use tbl, out of range indices are set to 0 in destination. The 2nd |
| // table vector lookup use tbx, out of range indices are unchanged in |
| // destination. Input [64..126] is mapped to index [65, 127] in second lookup. |
| // The value of index 64 is set to 0, so that we know that we already get the |
| // decoded data with the 1st lookup. |
| static const uint8_t fromBase64ForSIMD[128] = { |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 62u, 255u, 255u, 255u, 63u, |
| 52u, 53u, 54u, 55u, 56u, 57u, 58u, 59u, 60u, 61u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 0u, 255u, 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, |
| 14u, 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, |
| 40u, 41u, 42u, 43u, 44u, 45u, 46u, 47u, 48u, 49u, 50u, 51u, 255u, 255u, 255u, 255u, |
| }; |
| |
| static const uint8_t fromBase64URLForSIMD[128] = { |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 62u, 255u, 255u, |
| 52u, 53u, 54u, 55u, 56u, 57u, 58u, 59u, 60u, 61u, 255u, 255u, 255u, 255u, 255u, 255u, |
| 0u, 255u, 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, |
| 14u, 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 255u, 255u, 255u, 255u, |
| 63u, 255u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, |
| 40u, 41u, 42u, 43u, 44u, 45u, 46u, 47u, 48u, 49u, 50u, 51u, 255u, 255u, 255u, 255u, |
| }; |
| |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::base64_decodeBlock_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| Register src = c_rarg0; // source array |
| Register soff = c_rarg1; // source start offset |
| Register send = c_rarg2; // source end offset |
| Register dst = c_rarg3; // dest array |
| Register doff = c_rarg4; // position for writing to dest array |
| Register isURL = c_rarg5; // Base64 or URL character set |
| Register isMIME = c_rarg6; // Decoding MIME block - unused in this implementation |
| |
| Register length = send; // reuse send as length of source data to process |
| |
| Register simd_codec = c_rarg6; |
| Register nosimd_codec = c_rarg7; |
| |
| Label ProcessData, Process64B, Process32B, Process4B, SIMDEnter, SIMDExit, Exit; |
| |
| __ enter(); |
| |
| __ add(src, src, soff); |
| __ add(dst, dst, doff); |
| |
| __ mov(doff, dst); |
| |
| __ sub(length, send, soff); |
| __ bfm(length, zr, 0, 1); |
| |
| __ lea(nosimd_codec, ExternalAddress((address) fromBase64ForNoSIMD)); |
| __ cbz(isURL, ProcessData); |
| __ lea(nosimd_codec, ExternalAddress((address) fromBase64URLForNoSIMD)); |
| |
| __ BIND(ProcessData); |
| __ mov(rscratch1, length); |
| __ cmp(length, (u1)144); // 144 = 80 + 64 |
| __ br(Assembler::LT, Process4B); |
| |
| // In the MIME case, the line length cannot be more than 76 |
| // bytes (see RFC 2045). This is too short a block for SIMD |
| // to be worthwhile, so we use non-SIMD here. |
| __ movw(rscratch1, 79); |
| |
| __ BIND(Process4B); |
| __ ldrw(r14, __ post(src, 4)); |
| __ ubfxw(r10, r14, 0, 8); |
| __ ubfxw(r11, r14, 8, 8); |
| __ ubfxw(r12, r14, 16, 8); |
| __ ubfxw(r13, r14, 24, 8); |
| // get the de-code |
| __ ldrb(r10, Address(nosimd_codec, r10, Address::uxtw(0))); |
| __ ldrb(r11, Address(nosimd_codec, r11, Address::uxtw(0))); |
| __ ldrb(r12, Address(nosimd_codec, r12, Address::uxtw(0))); |
| __ ldrb(r13, Address(nosimd_codec, r13, Address::uxtw(0))); |
| // error detection, 255u indicates an illegal input |
| __ orrw(r14, r10, r11); |
| __ orrw(r15, r12, r13); |
| __ orrw(r14, r14, r15); |
| __ tbnz(r14, 7, Exit); |
| // recover the data |
| __ lslw(r14, r10, 10); |
| __ bfiw(r14, r11, 4, 6); |
| __ bfmw(r14, r12, 2, 5); |
| __ rev16w(r14, r14); |
| __ bfiw(r13, r12, 6, 2); |
| __ strh(r14, __ post(dst, 2)); |
| __ strb(r13, __ post(dst, 1)); |
| // non-simd loop |
| __ subsw(rscratch1, rscratch1, 4); |
| __ br(Assembler::GT, Process4B); |
| |
| // if exiting from PreProcess80B, rscratch1 == -1; |
| // otherwise, rscratch1 == 0. |
| __ cbzw(rscratch1, Exit); |
| __ sub(length, length, 80); |
| |
| __ lea(simd_codec, ExternalAddress((address) fromBase64ForSIMD)); |
| __ cbz(isURL, SIMDEnter); |
| __ lea(simd_codec, ExternalAddress((address) fromBase64URLForSIMD)); |
| |
| __ BIND(SIMDEnter); |
| __ ld1(v0, v1, v2, v3, __ T16B, __ post(simd_codec, 64)); |
| __ ld1(v4, v5, v6, v7, __ T16B, Address(simd_codec)); |
| __ mov(rscratch1, 63); |
| __ dup(v27, __ T16B, rscratch1); |
| |
| __ BIND(Process64B); |
| __ cmp(length, (u1)64); |
| __ br(Assembler::LT, Process32B); |
| generate_base64_decode_simdround(src, dst, v0, v4, 16, Exit); |
| __ sub(length, length, 64); |
| __ b(Process64B); |
| |
| __ BIND(Process32B); |
| __ cmp(length, (u1)32); |
| __ br(Assembler::LT, SIMDExit); |
| generate_base64_decode_simdround(src, dst, v0, v4, 8, Exit); |
| __ sub(length, length, 32); |
| __ b(Process32B); |
| |
| __ BIND(SIMDExit); |
| __ cbz(length, Exit); |
| __ movw(rscratch1, length); |
| __ b(Process4B); |
| |
| __ BIND(Exit); |
| __ sub(c_rarg0, dst, doff); |
| |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // Support for spin waits. |
| address generate_spin_wait() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::spin_wait_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| __ spin_wait(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| void generate_lookup_secondary_supers_table_stub() { |
| StubGenStubId stub_id = StubGenStubId::lookup_secondary_supers_table_id; |
| StubCodeMark mark(this, stub_id); |
| |
| const Register |
| r_super_klass = r0, |
| r_array_base = r1, |
| r_array_length = r2, |
| r_array_index = r3, |
| r_sub_klass = r4, |
| r_bitmap = rscratch2, |
| result = r5; |
| const FloatRegister |
| vtemp = v0; |
| |
| for (int slot = 0; slot < Klass::SECONDARY_SUPERS_TABLE_SIZE; slot++) { |
| StubRoutines::_lookup_secondary_supers_table_stubs[slot] = __ pc(); |
| Label L_success; |
| __ enter(); |
| __ lookup_secondary_supers_table_const(r_sub_klass, r_super_klass, |
| r_array_base, r_array_length, r_array_index, |
| vtemp, result, slot, |
| /*stub_is_near*/true); |
| __ leave(); |
| __ ret(lr); |
| } |
| } |
| |
| // Slow path implementation for UseSecondarySupersTable. |
| address generate_lookup_secondary_supers_table_slow_path_stub() { |
| StubGenStubId stub_id = StubGenStubId::lookup_secondary_supers_table_slow_path_id; |
| StubCodeMark mark(this, stub_id); |
| |
| address start = __ pc(); |
| const Register |
| r_super_klass = r0, // argument |
| r_array_base = r1, // argument |
| temp1 = r2, // temp |
| r_array_index = r3, // argument |
| r_bitmap = rscratch2, // argument |
| result = r5; // argument |
| |
| __ lookup_secondary_supers_table_slow_path(r_super_klass, r_array_base, r_array_index, r_bitmap, temp1, result); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| #if defined (LINUX) && !defined (__ARM_FEATURE_ATOMICS) |
| |
| // ARMv8.1 LSE versions of the atomic stubs used by Atomic::PlatformXX. |
| // |
| // If LSE is in use, generate LSE versions of all the stubs. The |
| // non-LSE versions are in atomic_aarch64.S. |
| |
| // class AtomicStubMark records the entry point of a stub and the |
| // stub pointer which will point to it. The stub pointer is set to |
| // the entry point when ~AtomicStubMark() is called, which must be |
| // after ICache::invalidate_range. This ensures safe publication of |
| // the generated code. |
| class AtomicStubMark { |
| address _entry_point; |
| aarch64_atomic_stub_t *_stub; |
| MacroAssembler *_masm; |
| public: |
| AtomicStubMark(MacroAssembler *masm, aarch64_atomic_stub_t *stub) { |
| _masm = masm; |
| __ align(32); |
| _entry_point = __ pc(); |
| _stub = stub; |
| } |
| ~AtomicStubMark() { |
| *_stub = (aarch64_atomic_stub_t)_entry_point; |
| } |
| }; |
| |
| // NB: For memory_order_conservative we need a trailing membar after |
| // LSE atomic operations but not a leading membar. |
| // |
| // We don't need a leading membar because a clause in the Arm ARM |
| // says: |
| // |
| // Barrier-ordered-before |
| // |
| // Barrier instructions order prior Memory effects before subsequent |
| // Memory effects generated by the same Observer. A read or a write |
| // RW1 is Barrier-ordered-before a read or a write RW 2 from the same |
| // Observer if and only if RW1 appears in program order before RW 2 |
| // and [ ... ] at least one of RW 1 and RW 2 is generated by an atomic |
| // instruction with both Acquire and Release semantics. |
| // |
| // All the atomic instructions {ldaddal, swapal, casal} have Acquire |
| // and Release semantics, therefore we don't need a leading |
| // barrier. However, there is no corresponding Barrier-ordered-after |
| // relationship, therefore we need a trailing membar to prevent a |
| // later store or load from being reordered with the store in an |
| // atomic instruction. |
| // |
| // This was checked by using the herd7 consistency model simulator |
| // (http://diy.inria.fr/) with this test case: |
| // |
| // AArch64 LseCas |
| // { 0:X1=x; 0:X2=y; 1:X1=x; 1:X2=y; } |
| // P0 | P1; |
| // LDR W4, [X2] | MOV W3, #0; |
| // DMB LD | MOV W4, #1; |
| // LDR W3, [X1] | CASAL W3, W4, [X1]; |
| // | DMB ISH; |
| // | STR W4, [X2]; |
| // exists |
| // (0:X3=0 /\ 0:X4=1) |
| // |
| // If X3 == 0 && X4 == 1, the store to y in P1 has been reordered |
| // with the store to x in P1. Without the DMB in P1 this may happen. |
| // |
| // At the time of writing we don't know of any AArch64 hardware that |
| // reorders stores in this way, but the Reference Manual permits it. |
| |
| void gen_cas_entry(Assembler::operand_size size, |
| atomic_memory_order order) { |
| Register prev = r3, ptr = c_rarg0, compare_val = c_rarg1, |
| exchange_val = c_rarg2; |
| bool acquire, release; |
| switch (order) { |
| case memory_order_relaxed: |
| acquire = false; |
| release = false; |
| break; |
| case memory_order_release: |
| acquire = false; |
| release = true; |
| break; |
| default: |
| acquire = true; |
| release = true; |
| break; |
| } |
| __ mov(prev, compare_val); |
| __ lse_cas(prev, exchange_val, ptr, size, acquire, release, /*not_pair*/true); |
| if (order == memory_order_conservative) { |
| __ membar(Assembler::StoreStore|Assembler::StoreLoad); |
| } |
| if (size == Assembler::xword) { |
| __ mov(r0, prev); |
| } else { |
| __ movw(r0, prev); |
| } |
| __ ret(lr); |
| } |
| |
| void gen_ldadd_entry(Assembler::operand_size size, atomic_memory_order order) { |
| Register prev = r2, addr = c_rarg0, incr = c_rarg1; |
| // If not relaxed, then default to conservative. Relaxed is the only |
| // case we use enough to be worth specializing. |
| if (order == memory_order_relaxed) { |
| __ ldadd(size, incr, prev, addr); |
| } else { |
| __ ldaddal(size, incr, prev, addr); |
| __ membar(Assembler::StoreStore|Assembler::StoreLoad); |
| } |
| if (size == Assembler::xword) { |
| __ mov(r0, prev); |
| } else { |
| __ movw(r0, prev); |
| } |
| __ ret(lr); |
| } |
| |
| void gen_swpal_entry(Assembler::operand_size size) { |
| Register prev = r2, addr = c_rarg0, incr = c_rarg1; |
| __ swpal(size, incr, prev, addr); |
| __ membar(Assembler::StoreStore|Assembler::StoreLoad); |
| if (size == Assembler::xword) { |
| __ mov(r0, prev); |
| } else { |
| __ movw(r0, prev); |
| } |
| __ ret(lr); |
| } |
| |
| void generate_atomic_entry_points() { |
| if (! UseLSE) { |
| return; |
| } |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::atomic_entry_points_id; |
| StubCodeMark mark(this, stub_id); |
| address first_entry = __ pc(); |
| |
| // ADD, memory_order_conservative |
| AtomicStubMark mark_fetch_add_4(_masm, &aarch64_atomic_fetch_add_4_impl); |
| gen_ldadd_entry(Assembler::word, memory_order_conservative); |
| AtomicStubMark mark_fetch_add_8(_masm, &aarch64_atomic_fetch_add_8_impl); |
| gen_ldadd_entry(Assembler::xword, memory_order_conservative); |
| |
| // ADD, memory_order_relaxed |
| AtomicStubMark mark_fetch_add_4_relaxed |
| (_masm, &aarch64_atomic_fetch_add_4_relaxed_impl); |
| gen_ldadd_entry(MacroAssembler::word, memory_order_relaxed); |
| AtomicStubMark mark_fetch_add_8_relaxed |
| (_masm, &aarch64_atomic_fetch_add_8_relaxed_impl); |
| gen_ldadd_entry(MacroAssembler::xword, memory_order_relaxed); |
| |
| // XCHG, memory_order_conservative |
| AtomicStubMark mark_xchg_4(_masm, &aarch64_atomic_xchg_4_impl); |
| gen_swpal_entry(Assembler::word); |
| AtomicStubMark mark_xchg_8_impl(_masm, &aarch64_atomic_xchg_8_impl); |
| gen_swpal_entry(Assembler::xword); |
| |
| // CAS, memory_order_conservative |
| AtomicStubMark mark_cmpxchg_1(_masm, &aarch64_atomic_cmpxchg_1_impl); |
| gen_cas_entry(MacroAssembler::byte, memory_order_conservative); |
| AtomicStubMark mark_cmpxchg_4(_masm, &aarch64_atomic_cmpxchg_4_impl); |
| gen_cas_entry(MacroAssembler::word, memory_order_conservative); |
| AtomicStubMark mark_cmpxchg_8(_masm, &aarch64_atomic_cmpxchg_8_impl); |
| gen_cas_entry(MacroAssembler::xword, memory_order_conservative); |
| |
| // CAS, memory_order_relaxed |
| AtomicStubMark mark_cmpxchg_1_relaxed |
| (_masm, &aarch64_atomic_cmpxchg_1_relaxed_impl); |
| gen_cas_entry(MacroAssembler::byte, memory_order_relaxed); |
| AtomicStubMark mark_cmpxchg_4_relaxed |
| (_masm, &aarch64_atomic_cmpxchg_4_relaxed_impl); |
| gen_cas_entry(MacroAssembler::word, memory_order_relaxed); |
| AtomicStubMark mark_cmpxchg_8_relaxed |
| (_masm, &aarch64_atomic_cmpxchg_8_relaxed_impl); |
| gen_cas_entry(MacroAssembler::xword, memory_order_relaxed); |
| |
| AtomicStubMark mark_cmpxchg_4_release |
| (_masm, &aarch64_atomic_cmpxchg_4_release_impl); |
| gen_cas_entry(MacroAssembler::word, memory_order_release); |
| AtomicStubMark mark_cmpxchg_8_release |
| (_masm, &aarch64_atomic_cmpxchg_8_release_impl); |
| gen_cas_entry(MacroAssembler::xword, memory_order_release); |
| |
| AtomicStubMark mark_cmpxchg_4_seq_cst |
| (_masm, &aarch64_atomic_cmpxchg_4_seq_cst_impl); |
| gen_cas_entry(MacroAssembler::word, memory_order_seq_cst); |
| AtomicStubMark mark_cmpxchg_8_seq_cst |
| (_masm, &aarch64_atomic_cmpxchg_8_seq_cst_impl); |
| gen_cas_entry(MacroAssembler::xword, memory_order_seq_cst); |
| |
| ICache::invalidate_range(first_entry, __ pc() - first_entry); |
| } |
| #endif // LINUX |
| |
| address generate_cont_thaw(Continuation::thaw_kind kind) { |
| bool return_barrier = Continuation::is_thaw_return_barrier(kind); |
| bool return_barrier_exception = Continuation::is_thaw_return_barrier_exception(kind); |
| |
| address start = __ pc(); |
| |
| if (return_barrier) { |
| __ ldr(rscratch1, Address(rthread, JavaThread::cont_entry_offset())); |
| __ mov(sp, rscratch1); |
| } |
| assert_asm(_masm, (__ ldr(rscratch1, Address(rthread, JavaThread::cont_entry_offset())), __ cmp(sp, rscratch1)), Assembler::EQ, "incorrect sp"); |
| |
| if (return_barrier) { |
| // preserve possible return value from a method returning to the return barrier |
| __ fmovd(rscratch1, v0); |
| __ stp(rscratch1, r0, Address(__ pre(sp, -2 * wordSize))); |
| } |
| |
| __ movw(c_rarg1, (return_barrier ? 1 : 0)); |
| __ call_VM_leaf(CAST_FROM_FN_PTR(address, Continuation::prepare_thaw), rthread, c_rarg1); |
| __ mov(rscratch2, r0); // r0 contains the size of the frames to thaw, 0 if overflow or no more frames |
| |
| if (return_barrier) { |
| // restore return value (no safepoint in the call to thaw, so even an oop return value should be OK) |
| __ ldp(rscratch1, r0, Address(__ post(sp, 2 * wordSize))); |
| __ fmovd(v0, rscratch1); |
| } |
| assert_asm(_masm, (__ ldr(rscratch1, Address(rthread, JavaThread::cont_entry_offset())), __ cmp(sp, rscratch1)), Assembler::EQ, "incorrect sp"); |
| |
| |
| Label thaw_success; |
| // rscratch2 contains the size of the frames to thaw, 0 if overflow or no more frames |
| __ cbnz(rscratch2, thaw_success); |
| __ lea(rscratch1, RuntimeAddress(SharedRuntime::throw_StackOverflowError_entry())); |
| __ br(rscratch1); |
| __ bind(thaw_success); |
| |
| // make room for the thawed frames |
| __ sub(rscratch1, sp, rscratch2); |
| __ andr(rscratch1, rscratch1, -16); // align |
| __ mov(sp, rscratch1); |
| |
| if (return_barrier) { |
| // save original return value -- again |
| __ fmovd(rscratch1, v0); |
| __ stp(rscratch1, r0, Address(__ pre(sp, -2 * wordSize))); |
| } |
| |
| // If we want, we can templatize thaw by kind, and have three different entries |
| __ movw(c_rarg1, (uint32_t)kind); |
| |
| __ call_VM_leaf(Continuation::thaw_entry(), rthread, c_rarg1); |
| __ mov(rscratch2, r0); // r0 is the sp of the yielding frame |
| |
| if (return_barrier) { |
| // restore return value (no safepoint in the call to thaw, so even an oop return value should be OK) |
| __ ldp(rscratch1, r0, Address(__ post(sp, 2 * wordSize))); |
| __ fmovd(v0, rscratch1); |
| } else { |
| __ mov(r0, zr); // return 0 (success) from doYield |
| } |
| |
| // we're now on the yield frame (which is in an address above us b/c rsp has been pushed down) |
| __ sub(sp, rscratch2, 2*wordSize); // now pointing to rfp spill |
| __ mov(rfp, sp); |
| |
| if (return_barrier_exception) { |
| __ ldr(c_rarg1, Address(rfp, wordSize)); // return address |
| __ authenticate_return_address(c_rarg1); |
| __ verify_oop(r0); |
| // save return value containing the exception oop in callee-saved R19 |
| __ mov(r19, r0); |
| |
| __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::exception_handler_for_return_address), rthread, c_rarg1); |
| |
| // Reinitialize the ptrue predicate register, in case the external runtime call clobbers ptrue reg, as we may return to SVE compiled code. |
| // __ reinitialize_ptrue(); |
| |
| // see OptoRuntime::generate_exception_blob: r0 -- exception oop, r3 -- exception pc |
| |
| __ mov(r1, r0); // the exception handler |
| __ mov(r0, r19); // restore return value containing the exception oop |
| __ verify_oop(r0); |
| |
| __ leave(); |
| __ mov(r3, lr); |
| __ br(r1); // the exception handler |
| } else { |
| // We're "returning" into the topmost thawed frame; see Thaw::push_return_frame |
| __ leave(); |
| __ ret(lr); |
| } |
| |
| return start; |
| } |
| |
| address generate_cont_thaw() { |
| if (!Continuations::enabled()) return nullptr; |
| |
| StubGenStubId stub_id = StubGenStubId::cont_thaw_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| generate_cont_thaw(Continuation::thaw_top); |
| return start; |
| } |
| |
| address generate_cont_returnBarrier() { |
| if (!Continuations::enabled()) return nullptr; |
| |
| // TODO: will probably need multiple return barriers depending on return type |
| StubGenStubId stub_id = StubGenStubId::cont_returnBarrier_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| generate_cont_thaw(Continuation::thaw_return_barrier); |
| |
| return start; |
| } |
| |
| address generate_cont_returnBarrier_exception() { |
| if (!Continuations::enabled()) return nullptr; |
| |
| StubGenStubId stub_id = StubGenStubId::cont_returnBarrierExc_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| generate_cont_thaw(Continuation::thaw_return_barrier_exception); |
| |
| return start; |
| } |
| |
| address generate_cont_preempt_stub() { |
| if (!Continuations::enabled()) return nullptr; |
| StubGenStubId stub_id = StubGenStubId::cont_preempt_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| __ reset_last_Java_frame(true); |
| |
| // Set sp to enterSpecial frame, i.e. remove all frames copied into the heap. |
| __ ldr(rscratch2, Address(rthread, JavaThread::cont_entry_offset())); |
| __ mov(sp, rscratch2); |
| |
| Label preemption_cancelled; |
| __ ldrb(rscratch1, Address(rthread, JavaThread::preemption_cancelled_offset())); |
| __ cbnz(rscratch1, preemption_cancelled); |
| |
| // Remove enterSpecial frame from the stack and return to Continuation.run() to unmount. |
| SharedRuntime::continuation_enter_cleanup(_masm); |
| __ leave(); |
| __ ret(lr); |
| |
| // We acquired the monitor after freezing the frames so call thaw to continue execution. |
| __ bind(preemption_cancelled); |
| __ strb(zr, Address(rthread, JavaThread::preemption_cancelled_offset())); |
| __ lea(rfp, Address(sp, checked_cast<int32_t>(ContinuationEntry::size()))); |
| __ lea(rscratch1, ExternalAddress(ContinuationEntry::thaw_call_pc_address())); |
| __ ldr(rscratch1, Address(rscratch1)); |
| __ br(rscratch1); |
| |
| return start; |
| } |
| |
| // In sun.security.util.math.intpoly.IntegerPolynomial1305, integers |
| // are represented as long[5], with BITS_PER_LIMB = 26. |
| // Pack five 26-bit limbs into three 64-bit registers. |
| void pack_26(Register dest0, Register dest1, Register dest2, Register src) { |
| __ ldp(dest0, rscratch1, Address(src, 0)); // 26 bits |
| __ add(dest0, dest0, rscratch1, Assembler::LSL, 26); // 26 bits |
| __ ldp(rscratch1, rscratch2, Address(src, 2 * sizeof (jlong))); |
| __ add(dest0, dest0, rscratch1, Assembler::LSL, 52); // 12 bits |
| |
| __ add(dest1, zr, rscratch1, Assembler::LSR, 12); // 14 bits |
| __ add(dest1, dest1, rscratch2, Assembler::LSL, 14); // 26 bits |
| __ ldr(rscratch1, Address(src, 4 * sizeof (jlong))); |
| __ add(dest1, dest1, rscratch1, Assembler::LSL, 40); // 24 bits |
| |
| if (dest2->is_valid()) { |
| __ add(dest2, zr, rscratch1, Assembler::LSR, 24); // 2 bits |
| } else { |
| #ifdef ASSERT |
| Label OK; |
| __ cmp(zr, rscratch1, Assembler::LSR, 24); // 2 bits |
| __ br(__ EQ, OK); |
| __ stop("high bits of Poly1305 integer should be zero"); |
| __ should_not_reach_here(); |
| __ bind(OK); |
| #endif |
| } |
| } |
| |
| // As above, but return only a 128-bit integer, packed into two |
| // 64-bit registers. |
| void pack_26(Register dest0, Register dest1, Register src) { |
| pack_26(dest0, dest1, noreg, src); |
| } |
| |
| // Multiply and multiply-accumulate unsigned 64-bit registers. |
| void wide_mul(Register prod_lo, Register prod_hi, Register n, Register m) { |
| __ mul(prod_lo, n, m); |
| __ umulh(prod_hi, n, m); |
| } |
| void wide_madd(Register sum_lo, Register sum_hi, Register n, Register m) { |
| wide_mul(rscratch1, rscratch2, n, m); |
| __ adds(sum_lo, sum_lo, rscratch1); |
| __ adc(sum_hi, sum_hi, rscratch2); |
| } |
| |
| // Poly1305, RFC 7539 |
| |
| // See https://loup-vaillant.fr/tutorials/poly1305-design for a |
| // description of the tricks used to simplify and accelerate this |
| // computation. |
| |
| address generate_poly1305_processBlocks() { |
| __ align(CodeEntryAlignment); |
| StubGenStubId stub_id = StubGenStubId::poly1305_processBlocks_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| Label here; |
| __ enter(); |
| RegSet callee_saved = RegSet::range(r19, r28); |
| __ push(callee_saved, sp); |
| |
| RegSetIterator<Register> regs = (RegSet::range(c_rarg0, r28) - r18_tls - rscratch1 - rscratch2).begin(); |
| |
| // Arguments |
| const Register input_start = *regs, length = *++regs, acc_start = *++regs, r_start = *++regs; |
| |
| // R_n is the 128-bit randomly-generated key, packed into two |
| // registers. The caller passes this key to us as long[5], with |
| // BITS_PER_LIMB = 26. |
| const Register R_0 = *++regs, R_1 = *++regs; |
| pack_26(R_0, R_1, r_start); |
| |
| // RR_n is (R_n >> 2) * 5 |
| const Register RR_0 = *++regs, RR_1 = *++regs; |
| __ lsr(RR_0, R_0, 2); |
| __ add(RR_0, RR_0, RR_0, Assembler::LSL, 2); |
| __ lsr(RR_1, R_1, 2); |
| __ add(RR_1, RR_1, RR_1, Assembler::LSL, 2); |
| |
| // U_n is the current checksum |
| const Register U_0 = *++regs, U_1 = *++regs, U_2 = *++regs; |
| pack_26(U_0, U_1, U_2, acc_start); |
| |
| static constexpr int BLOCK_LENGTH = 16; |
| Label DONE, LOOP; |
| |
| __ cmp(length, checked_cast<u1>(BLOCK_LENGTH)); |
| __ br(Assembler::LT, DONE); { |
| __ bind(LOOP); |
| |
| // S_n is to be the sum of U_n and the next block of data |
| const Register S_0 = *++regs, S_1 = *++regs, S_2 = *++regs; |
| __ ldp(S_0, S_1, __ post(input_start, 2 * wordSize)); |
| __ adds(S_0, U_0, S_0); |
| __ adcs(S_1, U_1, S_1); |
| __ adc(S_2, U_2, zr); |
| __ add(S_2, S_2, 1); |
| |
| const Register U_0HI = *++regs, U_1HI = *++regs; |
| |
| // NB: this logic depends on some of the special properties of |
| // Poly1305 keys. In particular, because we know that the top |
| // four bits of R_0 and R_1 are zero, we can add together |
| // partial products without any risk of needing to propagate a |
| // carry out. |
| wide_mul(U_0, U_0HI, S_0, R_0); wide_madd(U_0, U_0HI, S_1, RR_1); wide_madd(U_0, U_0HI, S_2, RR_0); |
| wide_mul(U_1, U_1HI, S_0, R_1); wide_madd(U_1, U_1HI, S_1, R_0); wide_madd(U_1, U_1HI, S_2, RR_1); |
| __ andr(U_2, R_0, 3); |
| __ mul(U_2, S_2, U_2); |
| |
| // Recycle registers S_0, S_1, S_2 |
| regs = (regs.remaining() + S_0 + S_1 + S_2).begin(); |
| |
| // Partial reduction mod 2**130 - 5 |
| __ adds(U_1, U_0HI, U_1); |
| __ adc(U_2, U_1HI, U_2); |
| // Sum now in U_2:U_1:U_0. |
| // Dead: U_0HI, U_1HI. |
| regs = (regs.remaining() + U_0HI + U_1HI).begin(); |
| |
| // U_2:U_1:U_0 += (U_2 >> 2) * 5 in two steps |
| |
| // First, U_2:U_1:U_0 += (U_2 >> 2) |
| __ lsr(rscratch1, U_2, 2); |
| __ andr(U_2, U_2, (u8)3); |
| __ adds(U_0, U_0, rscratch1); |
| __ adcs(U_1, U_1, zr); |
| __ adc(U_2, U_2, zr); |
| // Second, U_2:U_1:U_0 += (U_2 >> 2) << 2 |
| __ adds(U_0, U_0, rscratch1, Assembler::LSL, 2); |
| __ adcs(U_1, U_1, zr); |
| __ adc(U_2, U_2, zr); |
| |
| __ sub(length, length, checked_cast<u1>(BLOCK_LENGTH)); |
| __ cmp(length, checked_cast<u1>(BLOCK_LENGTH)); |
| __ br(~ Assembler::LT, LOOP); |
| } |
| |
| // Further reduce modulo 2^130 - 5 |
| __ lsr(rscratch1, U_2, 2); |
| __ add(rscratch1, rscratch1, rscratch1, Assembler::LSL, 2); // rscratch1 = U_2 * 5 |
| __ adds(U_0, U_0, rscratch1); // U_0 += U_2 * 5 |
| __ adcs(U_1, U_1, zr); |
| __ andr(U_2, U_2, (u1)3); |
| __ adc(U_2, U_2, zr); |
| |
| // Unpack the sum into five 26-bit limbs and write to memory. |
| __ ubfiz(rscratch1, U_0, 0, 26); |
| __ ubfx(rscratch2, U_0, 26, 26); |
| __ stp(rscratch1, rscratch2, Address(acc_start)); |
| __ ubfx(rscratch1, U_0, 52, 12); |
| __ bfi(rscratch1, U_1, 12, 14); |
| __ ubfx(rscratch2, U_1, 14, 26); |
| __ stp(rscratch1, rscratch2, Address(acc_start, 2 * sizeof (jlong))); |
| __ ubfx(rscratch1, U_1, 40, 24); |
| __ bfi(rscratch1, U_2, 24, 3); |
| __ str(rscratch1, Address(acc_start, 4 * sizeof (jlong))); |
| |
| __ bind(DONE); |
| __ pop(callee_saved, sp); |
| __ leave(); |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| // exception handler for upcall stubs |
| address generate_upcall_stub_exception_handler() { |
| StubGenStubId stub_id = StubGenStubId::upcall_stub_exception_handler_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| // Native caller has no idea how to handle exceptions, |
| // so we just crash here. Up to callee to catch exceptions. |
| __ verify_oop(r0); |
| __ movptr(rscratch1, CAST_FROM_FN_PTR(uint64_t, UpcallLinker::handle_uncaught_exception)); |
| __ blr(rscratch1); |
| __ should_not_reach_here(); |
| |
| return start; |
| } |
| |
| // load Method* target of MethodHandle |
| // j_rarg0 = jobject receiver |
| // rmethod = result |
| address generate_upcall_stub_load_target() { |
| StubGenStubId stub_id = StubGenStubId::upcall_stub_load_target_id; |
| StubCodeMark mark(this, stub_id); |
| address start = __ pc(); |
| |
| __ resolve_global_jobject(j_rarg0, rscratch1, rscratch2); |
| // Load target method from receiver |
| __ load_heap_oop(rmethod, Address(j_rarg0, java_lang_invoke_MethodHandle::form_offset()), rscratch1, rscratch2); |
| __ load_heap_oop(rmethod, Address(rmethod, java_lang_invoke_LambdaForm::vmentry_offset()), rscratch1, rscratch2); |
| __ load_heap_oop(rmethod, Address(rmethod, java_lang_invoke_MemberName::method_offset()), rscratch1, rscratch2); |
| __ access_load_at(T_ADDRESS, IN_HEAP, rmethod, |
| Address(rmethod, java_lang_invoke_ResolvedMethodName::vmtarget_offset()), |
| noreg, noreg); |
| __ str(rmethod, Address(rthread, JavaThread::callee_target_offset())); // just in case callee is deoptimized |
| |
| __ ret(lr); |
| |
| return start; |
| } |
| |
| #undef __ |
| #define __ masm-> |
| |
| class MontgomeryMultiplyGenerator : public MacroAssembler { |
| |
| Register Pa_base, Pb_base, Pn_base, Pm_base, inv, Rlen, Ra, Rb, Rm, Rn, |
| Pa, Pb, Pn, Pm, Rhi_ab, Rlo_ab, Rhi_mn, Rlo_mn, t0, t1, t2, Ri, Rj; |
| |
| RegSet _toSave; |
| bool _squaring; |
| |
| public: |
| MontgomeryMultiplyGenerator (Assembler *as, bool squaring) |
| : MacroAssembler(as->code()), _squaring(squaring) { |
| |
| // Register allocation |
| |
| RegSetIterator<Register> regs = (RegSet::range(r0, r26) - r18_tls).begin(); |
| Pa_base = *regs; // Argument registers |
| if (squaring) |
| Pb_base = Pa_base; |
| else |
| Pb_base = *++regs; |
| Pn_base = *++regs; |
| Rlen= *++regs; |
| inv = *++regs; |
| Pm_base = *++regs; |
| |
| // Working registers: |
| Ra = *++regs; // The current digit of a, b, n, and m. |
| Rb = *++regs; |
| Rm = *++regs; |
| Rn = *++regs; |
| |
| Pa = *++regs; // Pointers to the current/next digit of a, b, n, and m. |
| Pb = *++regs; |
| Pm = *++regs; |
| Pn = *++regs; |
| |
| t0 = *++regs; // Three registers which form a |
| t1 = *++regs; // triple-precision accumuator. |
| t2 = *++regs; |
| |
| Ri = *++regs; // Inner and outer loop indexes. |
| Rj = *++regs; |
| |
| Rhi_ab = *++regs; // Product registers: low and high parts |
| Rlo_ab = *++regs; // of a*b and m*n. |
| Rhi_mn = *++regs; |
| Rlo_mn = *++regs; |
| |
| // r19 and up are callee-saved. |
| _toSave = RegSet::range(r19, *regs) + Pm_base; |
| } |
| |
| private: |
| void save_regs() { |
| push(_toSave, sp); |
| } |
| |
| void restore_regs() { |
| pop(_toSave, sp); |
| } |
| |
| template <typename T> |
| void unroll_2(Register count, T block) { |
| Label loop, end, odd; |
| tbnz(count, 0, odd); |
| cbz(count, end); |
| align(16); |
| bind(loop); |
| (this->*block)(); |
| bind(odd); |
| (this->*block)(); |
| subs(count, count, 2); |
| br(Assembler::GT, loop); |
| bind(end); |
| } |
| |
| template <typename T> |
| void unroll_2(Register count, T block, Register d, Register s, Register tmp) { |
| Label loop, end, odd; |
| tbnz(count, 0, odd); |
| cbz(count, end); |
| align(16); |
| bind(loop); |
| (this->*block)(d, s, tmp); |
| bind(odd); |
| (this->*block)(d, s, tmp); |
| subs(count, count, 2); |
| br(Assembler::GT, loop); |
| bind(end); |
| } |
| |
| void pre1(RegisterOrConstant i) { |
| block_comment("pre1"); |
| // Pa = Pa_base; |
| // Pb = Pb_base + i; |
| // Pm = Pm_base; |
| // Pn = Pn_base + i; |
| // Ra = *Pa; |
| // Rb = *Pb; |
| // Rm = *Pm; |
| // Rn = *Pn; |
| ldr(Ra, Address(Pa_base)); |
| ldr(Rb, Address(Pb_base, i, Address::uxtw(LogBytesPerWord))); |
| ldr(Rm, Address(Pm_base)); |
| ldr(Rn, Address(Pn_base, i, Address::uxtw(LogBytesPerWord))); |
| lea(Pa, Address(Pa_base)); |
| lea(Pb, Address(Pb_base, i, Address::uxtw(LogBytesPerWord))); |
| lea(Pm, Address(Pm_base)); |
| lea(Pn, Address(Pn_base, i, Address::uxtw(LogBytesPerWord))); |
| |
| // Zero the m*n result. |
| mov(Rhi_mn, zr); |
| mov(Rlo_mn, zr); |
| } |
| |
| // The core multiply-accumulate step of a Montgomery |
| // multiplication. The idea is to schedule operations as a |
| // pipeline so that instructions with long latencies (loads and |
| // multiplies) have time to complete before their results are |
| // used. This most benefits in-order implementations of the |
| // architecture but out-of-order ones also benefit. |
| void step() { |
| block_comment("step"); |
| // MACC(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| umulh(Rhi_ab, Ra, Rb); |
| mul(Rlo_ab, Ra, Rb); |
| ldr(Ra, pre(Pa, wordSize)); |
| ldr(Rb, pre(Pb, -wordSize)); |
| acc(Rhi_mn, Rlo_mn, t0, t1, t2); // The pending m*n from the |
| // previous iteration. |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| umulh(Rhi_mn, Rm, Rn); |
| mul(Rlo_mn, Rm, Rn); |
| ldr(Rm, pre(Pm, wordSize)); |
| ldr(Rn, pre(Pn, -wordSize)); |
| acc(Rhi_ab, Rlo_ab, t0, t1, t2); |
| } |
| |
| void post1() { |
| block_comment("post1"); |
| |
| // MACC(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| umulh(Rhi_ab, Ra, Rb); |
| mul(Rlo_ab, Ra, Rb); |
| acc(Rhi_mn, Rlo_mn, t0, t1, t2); // The pending m*n |
| acc(Rhi_ab, Rlo_ab, t0, t1, t2); |
| |
| // *Pm = Rm = t0 * inv; |
| mul(Rm, t0, inv); |
| str(Rm, Address(Pm)); |
| |
| // MACC(Rm, Rn, t0, t1, t2); |
| // t0 = t1; t1 = t2; t2 = 0; |
| umulh(Rhi_mn, Rm, Rn); |
| |
| #ifndef PRODUCT |
| // assert(m[i] * n[0] + t0 == 0, "broken Montgomery multiply"); |
| { |
| mul(Rlo_mn, Rm, Rn); |
| add(Rlo_mn, t0, Rlo_mn); |
| Label ok; |
| cbz(Rlo_mn, ok); { |
| stop("broken Montgomery multiply"); |
| } bind(ok); |
| } |
| #endif |
| // We have very carefully set things up so that |
| // m[i]*n[0] + t0 == 0 (mod b), so we don't have to calculate |
| // the lower half of Rm * Rn because we know the result already: |
| // it must be -t0. t0 + (-t0) must generate a carry iff |
| // t0 != 0. So, rather than do a mul and an adds we just set |
| // the carry flag iff t0 is nonzero. |
| // |
| // mul(Rlo_mn, Rm, Rn); |
| // adds(zr, t0, Rlo_mn); |
| subs(zr, t0, 1); // Set carry iff t0 is nonzero |
| adcs(t0, t1, Rhi_mn); |
| adc(t1, t2, zr); |
| mov(t2, zr); |
| } |
| |
| void pre2(RegisterOrConstant i, RegisterOrConstant len) { |
| block_comment("pre2"); |
| // Pa = Pa_base + i-len; |
| // Pb = Pb_base + len; |
| // Pm = Pm_base + i-len; |
| // Pn = Pn_base + len; |
| |
| if (i.is_register()) { |
| sub(Rj, i.as_register(), len); |
| } else { |
| mov(Rj, i.as_constant()); |
| sub(Rj, Rj, len); |
| } |
| // Rj == i-len |
| |
| lea(Pa, Address(Pa_base, Rj, Address::uxtw(LogBytesPerWord))); |
| lea(Pb, Address(Pb_base, len, Address::uxtw(LogBytesPerWord))); |
| lea(Pm, Address(Pm_base, Rj, Address::uxtw(LogBytesPerWord))); |
| lea(Pn, Address(Pn_base, len, Address::uxtw(LogBytesPerWord))); |
| |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| ldr(Ra, pre(Pa, wordSize)); |
| ldr(Rb, pre(Pb, -wordSize)); |
| ldr(Rm, pre(Pm, wordSize)); |
| ldr(Rn, pre(Pn, -wordSize)); |
| |
| mov(Rhi_mn, zr); |
| mov(Rlo_mn, zr); |
| } |
| |
| void post2(RegisterOrConstant i, RegisterOrConstant len) { |
| block_comment("post2"); |
| if (i.is_constant()) { |
| mov(Rj, i.as_constant()-len.as_constant()); |
| } else { |
| sub(Rj, i.as_register(), len); |
| } |
| |
| adds(t0, t0, Rlo_mn); // The pending m*n, low part |
| |
| // As soon as we know the least significant digit of our result, |
| // store it. |
| // Pm_base[i-len] = t0; |
| str(t0, Address(Pm_base, Rj, Address::uxtw(LogBytesPerWord))); |
| |
| // t0 = t1; t1 = t2; t2 = 0; |
| adcs(t0, t1, Rhi_mn); // The pending m*n, high part |
| adc(t1, t2, zr); |
| mov(t2, zr); |
| } |
| |
| // A carry in t0 after Montgomery multiplication means that we |
| // should subtract multiples of n from our result in m. We'll |
| // keep doing that until there is no carry. |
| void normalize(RegisterOrConstant len) { |
| block_comment("normalize"); |
| // while (t0) |
| // t0 = sub(Pm_base, Pn_base, t0, len); |
| Label loop, post, again; |
| Register cnt = t1, i = t2; // Re-use registers; we're done with them now |
| cbz(t0, post); { |
| bind(again); { |
| mov(i, zr); |
| mov(cnt, len); |
| ldr(Rm, Address(Pm_base, i, Address::uxtw(LogBytesPerWord))); |
| ldr(Rn, Address(Pn_base, i, Address::uxtw(LogBytesPerWord))); |
| subs(zr, zr, zr); // set carry flag, i.e. no borrow |
| align(16); |
| bind(loop); { |
| sbcs(Rm, Rm, Rn); |
| str(Rm, Address(Pm_base, i, Address::uxtw(LogBytesPerWord))); |
| add(i, i, 1); |
| ldr(Rm, Address(Pm_base, i, Address::uxtw(LogBytesPerWord))); |
| ldr(Rn, Address(Pn_base, i, Address::uxtw(LogBytesPerWord))); |
| sub(cnt, cnt, 1); |
| } cbnz(cnt, loop); |
| sbc(t0, t0, zr); |
| } cbnz(t0, again); |
| } bind(post); |
| } |
| |
| // Move memory at s to d, reversing words. |
| // Increments d to end of copied memory |
| // Destroys tmp1, tmp2 |
| // Preserves len |
| // Leaves s pointing to the address which was in d at start |
| void reverse(Register d, Register s, Register len, Register tmp1, Register tmp2) { |
| assert(tmp1->encoding() < r19->encoding(), "register corruption"); |
| assert(tmp2->encoding() < r19->encoding(), "register corruption"); |
| |
| lea(s, Address(s, len, Address::uxtw(LogBytesPerWord))); |
| mov(tmp1, len); |
| unroll_2(tmp1, &MontgomeryMultiplyGenerator::reverse1, d, s, tmp2); |
| sub(s, d, len, ext::uxtw, LogBytesPerWord); |
| } |
| // where |
| void reverse1(Register d, Register s, Register tmp) { |
| ldr(tmp, pre(s, -wordSize)); |
| ror(tmp, tmp, 32); |
| str(tmp, post(d, wordSize)); |
| } |
| |
| void step_squaring() { |
| // An extra ACC |
| step(); |
| acc(Rhi_ab, Rlo_ab, t0, t1, t2); |
| } |
| |
| void last_squaring(RegisterOrConstant i) { |
| Label dont; |
| // if ((i & 1) == 0) { |
| tbnz(i.as_register(), 0, dont); { |
| // MACC(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| umulh(Rhi_ab, Ra, Rb); |
| mul(Rlo_ab, Ra, Rb); |
| acc(Rhi_ab, Rlo_ab, t0, t1, t2); |
| } bind(dont); |
| } |
| |
| void extra_step_squaring() { |
| acc(Rhi_mn, Rlo_mn, t0, t1, t2); // The pending m*n |
| |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| umulh(Rhi_mn, Rm, Rn); |
| mul(Rlo_mn, Rm, Rn); |
| ldr(Rm, pre(Pm, wordSize)); |
| ldr(Rn, pre(Pn, -wordSize)); |
| } |
| |
| void post1_squaring() { |
| acc(Rhi_mn, Rlo_mn, t0, t1, t2); // The pending m*n |
| |
| // *Pm = Rm = t0 * inv; |
| mul(Rm, t0, inv); |
| str(Rm, Address(Pm)); |
| |
| // MACC(Rm, Rn, t0, t1, t2); |
| // t0 = t1; t1 = t2; t2 = 0; |
| umulh(Rhi_mn, Rm, Rn); |
| |
| #ifndef PRODUCT |
| // assert(m[i] * n[0] + t0 == 0, "broken Montgomery multiply"); |
| { |
| mul(Rlo_mn, Rm, Rn); |
| add(Rlo_mn, t0, Rlo_mn); |
| Label ok; |
| cbz(Rlo_mn, ok); { |
| stop("broken Montgomery multiply"); |
| } bind(ok); |
| } |
| #endif |
| // We have very carefully set things up so that |
| // m[i]*n[0] + t0 == 0 (mod b), so we don't have to calculate |
| // the lower half of Rm * Rn because we know the result already: |
| // it must be -t0. t0 + (-t0) must generate a carry iff |
| // t0 != 0. So, rather than do a mul and an adds we just set |
| // the carry flag iff t0 is nonzero. |
| // |
| // mul(Rlo_mn, Rm, Rn); |
| // adds(zr, t0, Rlo_mn); |
| subs(zr, t0, 1); // Set carry iff t0 is nonzero |
| adcs(t0, t1, Rhi_mn); |
| adc(t1, t2, zr); |
| mov(t2, zr); |
| } |
| |
| void acc(Register Rhi, Register Rlo, |
| Register t0, Register t1, Register t2) { |
| adds(t0, t0, Rlo); |
| adcs(t1, t1, Rhi); |
| adc(t2, t2, zr); |
| } |
| |
| public: |
| /** |
| * Fast Montgomery multiplication. The derivation of the |
| * algorithm is in A Cryptographic Library for the Motorola |
| * DSP56000, Dusse and Kaliski, Proc. EUROCRYPT 90, pp. 230-237. |
| * |
| * Arguments: |
| * |
| * Inputs for multiplication: |
| * c_rarg0 - int array elements a |
| * c_rarg1 - int array elements b |
| * c_rarg2 - int array elements n (the modulus) |
| * c_rarg3 - int length |
| * c_rarg4 - int inv |
| * c_rarg5 - int array elements m (the result) |
| * |
| * Inputs for squaring: |
| * c_rarg0 - int array elements a |
| * c_rarg1 - int array elements n (the modulus) |
| * c_rarg2 - int length |
| * c_rarg3 - int inv |
| * c_rarg4 - int array elements m (the result) |
| * |
| */ |
| address generate_multiply() { |
| Label argh, nothing; |
| bind(argh); |
| stop("MontgomeryMultiply total_allocation must be <= 8192"); |
| |
| align(CodeEntryAlignment); |
| address entry = pc(); |
| |
| cbzw(Rlen, nothing); |
| |
| enter(); |
| |
| // Make room. |
| cmpw(Rlen, 512); |
| br(Assembler::HI, argh); |
| sub(Ra, sp, Rlen, ext::uxtw, exact_log2(4 * sizeof (jint))); |
| andr(sp, Ra, -2 * wordSize); |
| |
| lsrw(Rlen, Rlen, 1); // length in longwords = len/2 |
| |
| { |
| // Copy input args, reversing as we go. We use Ra as a |
| // temporary variable. |
| reverse(Ra, Pa_base, Rlen, t0, t1); |
| if (!_squaring) |
| reverse(Ra, Pb_base, Rlen, t0, t1); |
| reverse(Ra, Pn_base, Rlen, t0, t1); |
| } |
| |
| // Push all call-saved registers and also Pm_base which we'll need |
| // at the end. |
| save_regs(); |
| |
| #ifndef PRODUCT |
| // assert(inv * n[0] == -1UL, "broken inverse in Montgomery multiply"); |
| { |
| ldr(Rn, Address(Pn_base, 0)); |
| mul(Rlo_mn, Rn, inv); |
| subs(zr, Rlo_mn, -1); |
| Label ok; |
| br(EQ, ok); { |
| stop("broken inverse in Montgomery multiply"); |
| } bind(ok); |
| } |
| #endif |
| |
| mov(Pm_base, Ra); |
| |
| mov(t0, zr); |
| mov(t1, zr); |
| mov(t2, zr); |
| |
| block_comment("for (int i = 0; i < len; i++) {"); |
| mov(Ri, zr); { |
| Label loop, end; |
| cmpw(Ri, Rlen); |
| br(Assembler::GE, end); |
| |
| bind(loop); |
| pre1(Ri); |
| |
| block_comment(" for (j = i; j; j--) {"); { |
| movw(Rj, Ri); |
| unroll_2(Rj, &MontgomeryMultiplyGenerator::step); |
| } block_comment(" } // j"); |
| |
| post1(); |
| addw(Ri, Ri, 1); |
| cmpw(Ri, Rlen); |
| br(Assembler::LT, loop); |
| bind(end); |
| block_comment("} // i"); |
| } |
| |
| block_comment("for (int i = len; i < 2*len; i++) {"); |
| mov(Ri, Rlen); { |
| Label loop, end; |
| cmpw(Ri, Rlen, Assembler::LSL, 1); |
| br(Assembler::GE, end); |
| |
| bind(loop); |
| pre2(Ri, Rlen); |
| |
| block_comment(" for (j = len*2-i-1; j; j--) {"); { |
| lslw(Rj, Rlen, 1); |
| subw(Rj, Rj, Ri); |
| subw(Rj, Rj, 1); |
| unroll_2(Rj, &MontgomeryMultiplyGenerator::step); |
| } block_comment(" } // j"); |
| |
| post2(Ri, Rlen); |
| addw(Ri, Ri, 1); |
| cmpw(Ri, Rlen, Assembler::LSL, 1); |
| br(Assembler::LT, loop); |
| bind(end); |
| } |
| block_comment("} // i"); |
| |
| normalize(Rlen); |
| |
| mov(Ra, Pm_base); // Save Pm_base in Ra |
| restore_regs(); // Restore caller's Pm_base |
| |
| // Copy our result into caller's Pm_base |
| reverse(Pm_base, Ra, Rlen, t0, t1); |
| |
| leave(); |
| bind(nothing); |
| ret(lr); |
| |
| return entry; |
| } |
| // In C, approximately: |
| |
| // void |
| // montgomery_multiply(julong Pa_base[], julong Pb_base[], |
| // julong Pn_base[], julong Pm_base[], |
| // julong inv, int len) { |
| // julong t0 = 0, t1 = 0, t2 = 0; // Triple-precision accumulator |
| // julong *Pa, *Pb, *Pn, *Pm; |
| // julong Ra, Rb, Rn, Rm; |
| |
| // int i; |
| |
| // assert(inv * Pn_base[0] == -1UL, "broken inverse in Montgomery multiply"); |
| |
| // for (i = 0; i < len; i++) { |
| // int j; |
| |
| // Pa = Pa_base; |
| // Pb = Pb_base + i; |
| // Pm = Pm_base; |
| // Pn = Pn_base + i; |
| |
| // Ra = *Pa; |
| // Rb = *Pb; |
| // Rm = *Pm; |
| // Rn = *Pn; |
| |
| // int iters = i; |
| // for (j = 0; iters--; j++) { |
| // assert(Ra == Pa_base[j] && Rb == Pb_base[i-j], "must be"); |
| // MACC(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // assert(Rm == Pm_base[j] && Rn == Pn_base[i-j], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| // } |
| |
| // assert(Ra == Pa_base[i] && Rb == Pb_base[0], "must be"); |
| // MACC(Ra, Rb, t0, t1, t2); |
| // *Pm = Rm = t0 * inv; |
| // assert(Rm == Pm_base[i] && Rn == Pn_base[0], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| |
| // assert(t0 == 0, "broken Montgomery multiply"); |
| |
| // t0 = t1; t1 = t2; t2 = 0; |
| // } |
| |
| // for (i = len; i < 2*len; i++) { |
| // int j; |
| |
| // Pa = Pa_base + i-len; |
| // Pb = Pb_base + len; |
| // Pm = Pm_base + i-len; |
| // Pn = Pn_base + len; |
| |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| |
| // int iters = len*2-i-1; |
| // for (j = i-len+1; iters--; j++) { |
| // assert(Ra == Pa_base[j] && Rb == Pb_base[i-j], "must be"); |
| // MACC(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // assert(Rm == Pm_base[j] && Rn == Pn_base[i-j], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| // } |
| |
| // Pm_base[i-len] = t0; |
| // t0 = t1; t1 = t2; t2 = 0; |
| // } |
| |
| // while (t0) |
| // t0 = sub(Pm_base, Pn_base, t0, len); |
| // } |
| |
| /** |
| * Fast Montgomery squaring. This uses asymptotically 25% fewer |
| * multiplies than Montgomery multiplication so it should be up to |
| * 25% faster. However, its loop control is more complex and it |
| * may actually run slower on some machines. |
| * |
| * Arguments: |
| * |
| * Inputs: |
| * c_rarg0 - int array elements a |
| * c_rarg1 - int array elements n (the modulus) |
| * c_rarg2 - int length |
| * c_rarg3 - int inv |
| * c_rarg4 - int array elements m (the result) |
| * |
| */ |
| address generate_square() { |
| Label argh; |
| bind(argh); |
| stop("MontgomeryMultiply total_allocation must be <= 8192"); |
| |
| align(CodeEntryAlignment); |
| address entry = pc(); |
| |
| enter(); |
| |
| // Make room. |
| cmpw(Rlen, 512); |
| br(Assembler::HI, argh); |
| sub(Ra, sp, Rlen, ext::uxtw, exact_log2(4 * sizeof (jint))); |
| andr(sp, Ra, -2 * wordSize); |
| |
| lsrw(Rlen, Rlen, 1); // length in longwords = len/2 |
| |
| { |
| // Copy input args, reversing as we go. We use Ra as a |
| // temporary variable. |
| reverse(Ra, Pa_base, Rlen, t0, t1); |
| reverse(Ra, Pn_base, Rlen, t0, t1); |
| } |
| |
| // Push all call-saved registers and also Pm_base which we'll need |
| // at the end. |
| save_regs(); |
| |
| mov(Pm_base, Ra); |
| |
| mov(t0, zr); |
| mov(t1, zr); |
| mov(t2, zr); |
| |
| block_comment("for (int i = 0; i < len; i++) {"); |
| mov(Ri, zr); { |
| Label loop, end; |
| bind(loop); |
| cmp(Ri, Rlen); |
| br(Assembler::GE, end); |
| |
| pre1(Ri); |
| |
| block_comment("for (j = (i+1)/2; j; j--) {"); { |
| add(Rj, Ri, 1); |
| lsr(Rj, Rj, 1); |
| unroll_2(Rj, &MontgomeryMultiplyGenerator::step_squaring); |
| } block_comment(" } // j"); |
| |
| last_squaring(Ri); |
| |
| block_comment(" for (j = i/2; j; j--) {"); { |
| lsr(Rj, Ri, 1); |
| unroll_2(Rj, &MontgomeryMultiplyGenerator::extra_step_squaring); |
| } block_comment(" } // j"); |
| |
| post1_squaring(); |
| add(Ri, Ri, 1); |
| cmp(Ri, Rlen); |
| br(Assembler::LT, loop); |
| |
| bind(end); |
| block_comment("} // i"); |
| } |
| |
| block_comment("for (int i = len; i < 2*len; i++) {"); |
| mov(Ri, Rlen); { |
| Label loop, end; |
| bind(loop); |
| cmp(Ri, Rlen, Assembler::LSL, 1); |
| br(Assembler::GE, end); |
| |
| pre2(Ri, Rlen); |
| |
| block_comment(" for (j = (2*len-i-1)/2; j; j--) {"); { |
| lsl(Rj, Rlen, 1); |
| sub(Rj, Rj, Ri); |
| sub(Rj, Rj, 1); |
| lsr(Rj, Rj, 1); |
| unroll_2(Rj, &MontgomeryMultiplyGenerator::step_squaring); |
| } block_comment(" } // j"); |
| |
| last_squaring(Ri); |
| |
| block_comment(" for (j = (2*len-i)/2; j; j--) {"); { |
| lsl(Rj, Rlen, 1); |
| sub(Rj, Rj, Ri); |
| lsr(Rj, Rj, 1); |
| unroll_2(Rj, &MontgomeryMultiplyGenerator::extra_step_squaring); |
| } block_comment(" } // j"); |
| |
| post2(Ri, Rlen); |
| add(Ri, Ri, 1); |
| cmp(Ri, Rlen, Assembler::LSL, 1); |
| |
| br(Assembler::LT, loop); |
| bind(end); |
| block_comment("} // i"); |
| } |
| |
| normalize(Rlen); |
| |
| mov(Ra, Pm_base); // Save Pm_base in Ra |
| restore_regs(); // Restore caller's Pm_base |
| |
| // Copy our result into caller's Pm_base |
| reverse(Pm_base, Ra, Rlen, t0, t1); |
| |
| leave(); |
| ret(lr); |
| |
| return entry; |
| } |
| // In C, approximately: |
| |
| // void |
| // montgomery_square(julong Pa_base[], julong Pn_base[], |
| // julong Pm_base[], julong inv, int len) { |
| // julong t0 = 0, t1 = 0, t2 = 0; // Triple-precision accumulator |
| // julong *Pa, *Pb, *Pn, *Pm; |
| // julong Ra, Rb, Rn, Rm; |
| |
| // int i; |
| |
| // assert(inv * Pn_base[0] == -1UL, "broken inverse in Montgomery multiply"); |
| |
| // for (i = 0; i < len; i++) { |
| // int j; |
| |
| // Pa = Pa_base; |
| // Pb = Pa_base + i; |
| // Pm = Pm_base; |
| // Pn = Pn_base + i; |
| |
| // Ra = *Pa; |
| // Rb = *Pb; |
| // Rm = *Pm; |
| // Rn = *Pn; |
| |
| // int iters = (i+1)/2; |
| // for (j = 0; iters--; j++) { |
| // assert(Ra == Pa_base[j] && Rb == Pa_base[i-j], "must be"); |
| // MACC2(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // assert(Rm == Pm_base[j] && Rn == Pn_base[i-j], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| // } |
| // if ((i & 1) == 0) { |
| // assert(Ra == Pa_base[j], "must be"); |
| // MACC(Ra, Ra, t0, t1, t2); |
| // } |
| // iters = i/2; |
| // assert(iters == i-j, "must be"); |
| // for (; iters--; j++) { |
| // assert(Rm == Pm_base[j] && Rn == Pn_base[i-j], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| // } |
| |
| // *Pm = Rm = t0 * inv; |
| // assert(Rm == Pm_base[i] && Rn == Pn_base[0], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| |
| // assert(t0 == 0, "broken Montgomery multiply"); |
| |
| // t0 = t1; t1 = t2; t2 = 0; |
| // } |
| |
| // for (i = len; i < 2*len; i++) { |
| // int start = i-len+1; |
| // int end = start + (len - start)/2; |
| // int j; |
| |
| // Pa = Pa_base + i-len; |
| // Pb = Pa_base + len; |
| // Pm = Pm_base + i-len; |
| // Pn = Pn_base + len; |
| |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| |
| // int iters = (2*len-i-1)/2; |
| // assert(iters == end-start, "must be"); |
| // for (j = start; iters--; j++) { |
| // assert(Ra == Pa_base[j] && Rb == Pa_base[i-j], "must be"); |
| // MACC2(Ra, Rb, t0, t1, t2); |
| // Ra = *++Pa; |
| // Rb = *--Pb; |
| // assert(Rm == Pm_base[j] && Rn == Pn_base[i-j], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| // } |
| // if ((i & 1) == 0) { |
| // assert(Ra == Pa_base[j], "must be"); |
| // MACC(Ra, Ra, t0, t1, t2); |
| // } |
| // iters = (2*len-i)/2; |
| // assert(iters == len-j, "must be"); |
| // for (; iters--; j++) { |
| // assert(Rm == Pm_base[j] && Rn == Pn_base[i-j], "must be"); |
| // MACC(Rm, Rn, t0, t1, t2); |
| // Rm = *++Pm; |
| // Rn = *--Pn; |
| // } |
| // Pm_base[i-len] = t0; |
| // t0 = t1; t1 = t2; t2 = 0; |
| // } |
| |
| // while (t0) |
| // t0 = sub(Pm_base, Pn_base, t0, len); |
| // } |
| }; |
| |
| // Initialization |
| void generate_initial_stubs() { |
| // Generate initial stubs and initializes the entry points |
| |
| // entry points that exist in all platforms Note: This is code |
| // that could be shared among different platforms - however the |
| // benefit seems to be smaller than the disadvantage of having a |
| // much more complicated generator structure. See also comment in |
| // stubRoutines.hpp. |
| |
| StubRoutines::_forward_exception_entry = generate_forward_exception(); |
| |
| StubRoutines::_call_stub_entry = |
| generate_call_stub(StubRoutines::_call_stub_return_address); |
| |
| // is referenced by megamorphic call |
| StubRoutines::_catch_exception_entry = generate_catch_exception(); |
| |
| // Initialize table for copy memory (arraycopy) check. |
| if (UnsafeMemoryAccess::_table == nullptr) { |
| UnsafeMemoryAccess::create_table(8 + 4); // 8 for copyMemory; 4 for setMemory |
| } |
| |
| if (UseCRC32Intrinsics) { |
| // set table address before stub generation which use it |
| StubRoutines::_crc_table_adr = (address)StubRoutines::aarch64::_crc_table; |
| StubRoutines::_updateBytesCRC32 = generate_updateBytesCRC32(); |
| } |
| |
| if (UseCRC32CIntrinsics) { |
| StubRoutines::_updateBytesCRC32C = generate_updateBytesCRC32C(); |
| } |
| |
| if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_dsin)) { |
| StubRoutines::_dsin = generate_dsin_dcos(/* isCos = */ false); |
| } |
| |
| if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_dcos)) { |
| StubRoutines::_dcos = generate_dsin_dcos(/* isCos = */ true); |
| } |
| |
| if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_float16ToFloat) && |
| vmIntrinsics::is_intrinsic_available(vmIntrinsics::_floatToFloat16)) { |
| StubRoutines::_hf2f = generate_float16ToFloat(); |
| StubRoutines::_f2hf = generate_floatToFloat16(); |
| } |
| } |
| |
| void generate_continuation_stubs() { |
| // Continuation stubs: |
| StubRoutines::_cont_thaw = generate_cont_thaw(); |
| StubRoutines::_cont_returnBarrier = generate_cont_returnBarrier(); |
| StubRoutines::_cont_returnBarrierExc = generate_cont_returnBarrier_exception(); |
| StubRoutines::_cont_preempt_stub = generate_cont_preempt_stub(); |
| } |
| |
| void generate_final_stubs() { |
| // support for verify_oop (must happen after universe_init) |
| if (VerifyOops) { |
| StubRoutines::_verify_oop_subroutine_entry = generate_verify_oop(); |
| } |
| |
| // arraycopy stubs used by compilers |
| generate_arraycopy_stubs(); |
| |
| StubRoutines::_method_entry_barrier = generate_method_entry_barrier(); |
| |
| StubRoutines::aarch64::_spin_wait = generate_spin_wait(); |
| |
| StubRoutines::_upcall_stub_exception_handler = generate_upcall_stub_exception_handler(); |
| StubRoutines::_upcall_stub_load_target = generate_upcall_stub_load_target(); |
| |
| #if defined (LINUX) && !defined (__ARM_FEATURE_ATOMICS) |
| |
| generate_atomic_entry_points(); |
| |
| #endif // LINUX |
| |
| #ifdef COMPILER2 |
| if (UseSecondarySupersTable) { |
| StubRoutines::_lookup_secondary_supers_table_slow_path_stub = generate_lookup_secondary_supers_table_slow_path_stub(); |
| if (! InlineSecondarySupersTest) { |
| generate_lookup_secondary_supers_table_stub(); |
| } |
| } |
| #endif |
| |
| StubRoutines::_unsafe_setmemory = generate_unsafe_setmemory(); |
| |
| StubRoutines::aarch64::set_completed(); // Inidicate that arraycopy and zero_blocks stubs are generated |
| } |
| |
| void generate_compiler_stubs() { |
| #if COMPILER2_OR_JVMCI |
| |
| if (UseSVE == 0) { |
| StubRoutines::aarch64::_vector_iota_indices = generate_iota_indices(StubGenStubId::vector_iota_indices_id); |
| } |
| |
| // array equals stub for large arrays. |
| if (!UseSimpleArrayEquals) { |
| StubRoutines::aarch64::_large_array_equals = generate_large_array_equals(); |
| } |
| |
| // arrays_hascode stub for large arrays. |
| StubRoutines::aarch64::_large_arrays_hashcode_boolean = generate_large_arrays_hashcode(T_BOOLEAN); |
| StubRoutines::aarch64::_large_arrays_hashcode_byte = generate_large_arrays_hashcode(T_BYTE); |
| StubRoutines::aarch64::_large_arrays_hashcode_char = generate_large_arrays_hashcode(T_CHAR); |
| StubRoutines::aarch64::_large_arrays_hashcode_int = generate_large_arrays_hashcode(T_INT); |
| StubRoutines::aarch64::_large_arrays_hashcode_short = generate_large_arrays_hashcode(T_SHORT); |
| |
| // byte_array_inflate stub for large arrays. |
| StubRoutines::aarch64::_large_byte_array_inflate = generate_large_byte_array_inflate(); |
| |
| // countPositives stub for large arrays. |
| StubRoutines::aarch64::_count_positives = generate_count_positives(StubRoutines::aarch64::_count_positives_long); |
| |
| generate_compare_long_strings(); |
| |
| generate_string_indexof_stubs(); |
| |
| #ifdef COMPILER2 |
| if (UseMultiplyToLenIntrinsic) { |
| StubRoutines::_multiplyToLen = generate_multiplyToLen(); |
| } |
| |
| if (UseSquareToLenIntrinsic) { |
| StubRoutines::_squareToLen = generate_squareToLen(); |
| } |
| |
| if (UseMulAddIntrinsic) { |
| StubRoutines::_mulAdd = generate_mulAdd(); |
| } |
| |
| if (UseSIMDForBigIntegerShiftIntrinsics) { |
| StubRoutines::_bigIntegerRightShiftWorker = generate_bigIntegerRightShift(); |
| StubRoutines::_bigIntegerLeftShiftWorker = generate_bigIntegerLeftShift(); |
| } |
| |
| if (UseMontgomeryMultiplyIntrinsic) { |
| StubGenStubId stub_id = StubGenStubId::montgomeryMultiply_id; |
| StubCodeMark mark(this, stub_id); |
| MontgomeryMultiplyGenerator g(_masm, /*squaring*/false); |
| StubRoutines::_montgomeryMultiply = g.generate_multiply(); |
| } |
| |
| if (UseMontgomerySquareIntrinsic) { |
| StubGenStubId stub_id = StubGenStubId::montgomerySquare_id; |
| StubCodeMark mark(this, stub_id); |
| MontgomeryMultiplyGenerator g(_masm, /*squaring*/true); |
| // We use generate_multiply() rather than generate_square() |
| // because it's faster for the sizes of modulus we care about. |
| StubRoutines::_montgomerySquare = g.generate_multiply(); |
| } |
| |
| #endif // COMPILER2 |
| |
| if (UseChaCha20Intrinsics) { |
| StubRoutines::_chacha20Block = generate_chacha20Block_blockpar(); |
| } |
| |
| if (UseKyberIntrinsics) { |
| StubRoutines::_kyberNtt = generate_kyberNtt(); |
| StubRoutines::_kyberInverseNtt = generate_kyberInverseNtt(); |
| StubRoutines::_kyberNttMult = generate_kyberNttMult(); |
| StubRoutines::_kyberAddPoly_2 = generate_kyberAddPoly_2(); |
| StubRoutines::_kyberAddPoly_3 = generate_kyberAddPoly_3(); |
| StubRoutines::_kyber12To16 = generate_kyber12To16(); |
| StubRoutines::_kyberBarrettReduce = generate_kyberBarrettReduce(); |
| } |
| |
| if (UseDilithiumIntrinsics) { |
| StubRoutines::_dilithiumAlmostNtt = generate_dilithiumAlmostNtt(); |
| StubRoutines::_dilithiumAlmostInverseNtt = generate_dilithiumAlmostInverseNtt(); |
| StubRoutines::_dilithiumNttMult = generate_dilithiumNttMult(); |
| StubRoutines::_dilithiumMontMulByConstant = generate_dilithiumMontMulByConstant(); |
| StubRoutines::_dilithiumDecomposePoly = generate_dilithiumDecomposePoly(); |
| } |
| |
| if (UseBASE64Intrinsics) { |
| StubRoutines::_base64_encodeBlock = generate_base64_encodeBlock(); |
| StubRoutines::_base64_decodeBlock = generate_base64_decodeBlock(); |
| } |
| |
| // data cache line writeback |
| StubRoutines::_data_cache_writeback = generate_data_cache_writeback(); |
| StubRoutines::_data_cache_writeback_sync = generate_data_cache_writeback_sync(); |
| |
| if (UseAESIntrinsics) { |
| StubRoutines::_aescrypt_encryptBlock = generate_aescrypt_encryptBlock(); |
| StubRoutines::_aescrypt_decryptBlock = generate_aescrypt_decryptBlock(); |
| StubRoutines::_cipherBlockChaining_encryptAESCrypt = generate_cipherBlockChaining_encryptAESCrypt(); |
| StubRoutines::_cipherBlockChaining_decryptAESCrypt = generate_cipherBlockChaining_decryptAESCrypt(); |
| StubRoutines::_counterMode_AESCrypt = generate_counterMode_AESCrypt(); |
| } |
| if (UseGHASHIntrinsics) { |
| // StubRoutines::_ghash_processBlocks = generate_ghash_processBlocks(); |
| StubRoutines::_ghash_processBlocks = generate_ghash_processBlocks_wide(); |
| } |
| if (UseAESIntrinsics && UseGHASHIntrinsics) { |
| StubRoutines::_galoisCounterMode_AESCrypt = generate_galoisCounterMode_AESCrypt(); |
| } |
| |
| if (UseMD5Intrinsics) { |
| StubRoutines::_md5_implCompress = generate_md5_implCompress(StubGenStubId::md5_implCompress_id); |
| StubRoutines::_md5_implCompressMB = generate_md5_implCompress(StubGenStubId::md5_implCompressMB_id); |
| } |
| if (UseSHA1Intrinsics) { |
| StubRoutines::_sha1_implCompress = generate_sha1_implCompress(StubGenStubId::sha1_implCompress_id); |
| StubRoutines::_sha1_implCompressMB = generate_sha1_implCompress(StubGenStubId::sha1_implCompressMB_id); |
| } |
| if (UseSHA256Intrinsics) { |
| StubRoutines::_sha256_implCompress = generate_sha256_implCompress(StubGenStubId::sha256_implCompress_id); |
| StubRoutines::_sha256_implCompressMB = generate_sha256_implCompress(StubGenStubId::sha256_implCompressMB_id); |
| } |
| if (UseSHA512Intrinsics) { |
| StubRoutines::_sha512_implCompress = generate_sha512_implCompress(StubGenStubId::sha512_implCompress_id); |
| StubRoutines::_sha512_implCompressMB = generate_sha512_implCompress(StubGenStubId::sha512_implCompressMB_id); |
| } |
| if (UseSHA3Intrinsics) { |
| |
| StubRoutines::_double_keccak = generate_double_keccak(); |
| if (UseSIMDForSHA3Intrinsic) { |
| StubRoutines::_sha3_implCompress = generate_sha3_implCompress(StubGenStubId::sha3_implCompress_id); |
| StubRoutines::_sha3_implCompressMB = generate_sha3_implCompress(StubGenStubId::sha3_implCompressMB_id); |
| } else { |
| StubRoutines::_sha3_implCompress = generate_sha3_implCompress_gpr(StubGenStubId::sha3_implCompress_id); |
| StubRoutines::_sha3_implCompressMB = generate_sha3_implCompress_gpr(StubGenStubId::sha3_implCompressMB_id); |
| } |
| } |
| |
| if (UsePoly1305Intrinsics) { |
| StubRoutines::_poly1305_processBlocks = generate_poly1305_processBlocks(); |
| } |
| |
| // generate Adler32 intrinsics code |
| if (UseAdler32Intrinsics) { |
| StubRoutines::_updateBytesAdler32 = generate_updateBytesAdler32(); |
| } |
| |
| #endif // COMPILER2_OR_JVMCI |
| } |
| |
| public: |
| StubGenerator(CodeBuffer* code, StubGenBlobId blob_id) : StubCodeGenerator(code, blob_id) { |
| switch(blob_id) { |
| case initial_id: |
| generate_initial_stubs(); |
| break; |
| case continuation_id: |
| generate_continuation_stubs(); |
| break; |
| case compiler_id: |
| generate_compiler_stubs(); |
| break; |
| case final_id: |
| generate_final_stubs(); |
| break; |
| default: |
| fatal("unexpected blob id: %d", blob_id); |
| break; |
| }; |
| } |
| }; // end class declaration |
| |
| void StubGenerator_generate(CodeBuffer* code, StubGenBlobId blob_id) { |
| StubGenerator g(code, blob_id); |
| } |
| |
| |
| #if defined (LINUX) |
| |
| // Define pointers to atomic stubs and initialize them to point to the |
| // code in atomic_aarch64.S. |
| |
| #define DEFAULT_ATOMIC_OP(OPNAME, SIZE, RELAXED) \ |
| extern "C" uint64_t aarch64_atomic_ ## OPNAME ## _ ## SIZE ## RELAXED ## _default_impl \ |
| (volatile void *ptr, uint64_t arg1, uint64_t arg2); \ |
| aarch64_atomic_stub_t aarch64_atomic_ ## OPNAME ## _ ## SIZE ## RELAXED ## _impl \ |
| = aarch64_atomic_ ## OPNAME ## _ ## SIZE ## RELAXED ## _default_impl; |
| |
| DEFAULT_ATOMIC_OP(fetch_add, 4, ) |
| DEFAULT_ATOMIC_OP(fetch_add, 8, ) |
| DEFAULT_ATOMIC_OP(fetch_add, 4, _relaxed) |
| DEFAULT_ATOMIC_OP(fetch_add, 8, _relaxed) |
| DEFAULT_ATOMIC_OP(xchg, 4, ) |
| DEFAULT_ATOMIC_OP(xchg, 8, ) |
| DEFAULT_ATOMIC_OP(cmpxchg, 1, ) |
| DEFAULT_ATOMIC_OP(cmpxchg, 4, ) |
| DEFAULT_ATOMIC_OP(cmpxchg, 8, ) |
| DEFAULT_ATOMIC_OP(cmpxchg, 1, _relaxed) |
| DEFAULT_ATOMIC_OP(cmpxchg, 4, _relaxed) |
| DEFAULT_ATOMIC_OP(cmpxchg, 8, _relaxed) |
| DEFAULT_ATOMIC_OP(cmpxchg, 4, _release) |
| DEFAULT_ATOMIC_OP(cmpxchg, 8, _release) |
| DEFAULT_ATOMIC_OP(cmpxchg, 4, _seq_cst) |
| DEFAULT_ATOMIC_OP(cmpxchg, 8, _seq_cst) |
| |
| #undef DEFAULT_ATOMIC_OP |
| |
| #endif // LINUX |