/[cmucl]/src/lisp/ppc-arch.c
ViewVC logotype

Contents of /src/lisp/ppc-arch.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.14 - (show annotations)
Wed Nov 12 15:04:24 2008 UTC (5 years, 5 months ago) by rtoy
Branch: MAIN
CVS Tags: sparc-tramp-assem-base, post-merge-intl-branch, merged-unicode-utf16-extfmt-2009-06-11, intl-branch-working-2010-02-19-1000, unicode-string-buffer-impl-base, release-20b-pre1, release-20b-pre2, unicode-string-buffer-base, sparc-tramp-assem-2010-07-19, amd64-dd-start, release-19f-pre1, snapshot-2008-12, intl-2-branch-base, GIT-CONVERSION, cross-sol-x86-merged, label-2009-03-16, release-19f-base, merge-sse2-packed, merge-with-19f, intl-branch-working-2010-02-11-1000, RELEASE_20b, RELEASE_19f, release-20a-base, cross-sol-x86-base, snapshot-2010-12, snapshot-2010-11, snapshot-2011-09, snapshot-2011-06, snapshot-2011-07, snapshot-2011-04, snapshot-2011-02, snapshot-2011-03, snapshot-2011-01, pre-merge-intl-branch, snapshot-2010-05, snapshot-2010-04, snapshot-2010-07, snapshot-2010-06, snapshot-2010-01, snapshot-2010-03, snapshot-2010-02, snapshot-2010-08, label-2009-03-25, cross-sol-x86-2010-12-20, intl-branch-2010-03-18-1300, RELEASE_20a, release-20a-pre1, snapshot-2009-11, snapshot-2009-12, portable-clx-import-2009-06-16, cross-sparc-branch-base, intl-branch-base, portable-clx-base, snapshot-2009-08, snapshot-2009-02, snapshot-2009-01, snapshot-2009-07, snapshot-2009-05, snapshot-2009-04, HEAD
Branch point for: RELEASE-19F-BRANCH, portable-clx-branch, cross-sparc-branch, RELEASE-20B-BRANCH, unicode-string-buffer-branch, sparc-tramp-assem-branch, RELEASE-20A-BRANCH, amd64-dd-branch, unicode-string-buffer-impl-branch, intl-branch, cross-sol-x86-branch, intl-2-branch
Changes since 1.13: +2 -2 lines
File MIME type: text/plain
Merge in SSE2 changes from sse2-packed-branch (tag
sse2-packed-2008-11-12).
1 /*
2
3 $Header: /tiger/var/lib/cvsroots/cmucl/src/lisp/ppc-arch.c,v 1.14 2008/11/12 15:04:24 rtoy Rel $
4
5 This code was written as part of the CMU Common Lisp project at
6 Carnegie Mellon University, and has been placed in the public domain.
7
8 */
9
10 #include <stdio.h>
11
12 #include "arch.h"
13 #include "lisp.h"
14 #include "internals.h"
15 #include "globals.h"
16 #include "validate.h"
17 #include "os.h"
18 #include "lispregs.h"
19 #include "signal.h"
20 #include "interrupt.h"
21 #include "interr.h"
22
23 /* The header files may not define PT_DAR/PT_DSISR. This definition
24 is correct for all versions of ppc linux >= 2.0.30
25
26 As of DR2.1u4, MkLinux doesn't pass these registers to signal
27 handlers correctly; a patch is necessary in order to (partially)
28 correct this.
29
30 Even with the patch, the DSISR may not have its 'write' bit set
31 correctly (it tends not to be set if the fault was caused by
32 something other than a protection violation.)
33
34 Caveat callers. */
35
36 #ifndef PT_DAR
37 #define PT_DAR 41
38 #endif
39
40 #ifndef PT_DSISR
41 #define PT_DSISR 42
42 #endif
43
44 /*
45 * A macro to generate the instruction
46 *
47 * twllei r0, code
48 *
49 * This is what the ppc port uses to signal various traps like
50 * breakpoints and stuff.
51 */
52 #define TWLLEI_R0(code) ((3<<26) | (6 << 21) | code)
53
54 char *
55 arch_init(fpu_mode_t mode)
56 {
57 return "lisp.core";
58 }
59
60 os_vm_address_t arch_get_bad_addr(HANDLER_ARGS)
61 {
62 os_vm_address_t addr;
63
64 addr = (os_vm_address_t) SC_REG(context, PT_DAR);
65 return addr;
66 }
67
68
69 void
70 arch_skip_instruction(os_context_t * context)
71 {
72 /* Skip the offending instruction */
73 SC_PC(context) += 4;
74 }
75
76 unsigned char *
77 arch_internal_error_arguments(os_context_t * scp)
78 {
79 return (unsigned char *) (SC_PC(scp) + 4);
80 }
81
82 boolean arch_pseudo_atomic_atomic(os_context_t * scp)
83 {
84 return (SC_REG(scp, reg_ALLOC) & 4);
85 }
86
87 #define PSEUDO_ATOMIC_INTERRUPTED_BIAS 0x7f000000
88
89 void
90 arch_set_pseudo_atomic_interrupted(os_context_t * scp)
91 {
92 #if 0
93 SC_REG(scp, reg_NL3) += PSEUDO_ATOMIC_INTERRUPTED_BIAS;
94 #else
95 SC_REG(scp, reg_ALLOC) |= 1;
96 #endif
97 }
98
99 unsigned long
100 arch_install_breakpoint(void *pc)
101 {
102 unsigned long *ptr = (unsigned long *) pc;
103 unsigned long result = *ptr;
104
105 /*
106 * Insert a twllei r0, trap_Breakpoint instruction.
107 */
108 *ptr = TWLLEI_R0(trap_Breakpoint);
109 os_flush_icache((os_vm_address_t) pc, sizeof(unsigned long));
110
111 return result;
112 }
113
114 void
115 arch_remove_breakpoint(void *pc, unsigned long orig_inst)
116 {
117 *(unsigned long *) pc = orig_inst;
118 os_flush_icache((os_vm_address_t) pc, sizeof(unsigned long));
119 }
120
121 static unsigned long *skipped_break_addr, displaced_after_inst;
122 static sigset_t orig_sigmask;
123
124 void
125 arch_do_displaced_inst(os_context_t * scp, unsigned long orig_inst)
126 {
127 unsigned int *pc = (unsigned long *) SC_PC(scp);
128
129 orig_sigmask = scp->uc_sigmask;
130 sigemptyset(&scp->uc_sigmask);
131 FILLBLOCKSET(&scp->uc_sigmask);
132
133 /* Put the original instruction back */
134 *pc = orig_inst;
135 os_flush_icache((os_vm_address_t) pc, sizeof(unsigned int));
136
137 skipped_break_addr = pc;
138
139 /*
140 * Replace the next instruction with a
141 * twllei r0, trap_AfterBreakpoint
142 */
143 displaced_after_inst = *++pc;
144 *pc = TWLLEI_R0(trap_AfterBreakpoint);
145 os_flush_icache((os_vm_address_t) pc, sizeof(unsigned int));
146
147 sigreturn(scp);
148 }
149
150 #ifdef GENCGC
151 /*
152 * Return non-zero if the current instruction is an allocation trap
153 */
154 static int
155 allocation_trap_p(os_context_t * context)
156 {
157 int result;
158 unsigned int *pc;
159 unsigned inst;
160 unsigned opcode;
161 unsigned src;
162 unsigned dst;
163
164 result = 0;
165
166 /*
167 * First, the instruction has to be a TWLGE temp, NL3, which as the
168 * format.
169 * | 6| 5| 5 | 5 | 10|1| width
170 * |31|5 |dst|src| 4|0| field
171 */
172 pc = (unsigned int *) SC_PC(context);
173 inst = *pc;
174
175 #if 0
176 fprintf(stderr, "allocation_trap_p at %p: inst = 0x%08x\n", pc, inst);
177 #endif
178
179 opcode = inst >> 26;
180 src = (inst >> 11) & 0x1f;
181 dst = (inst >> 16) & 0x1f;
182 if ((opcode == 31) && (src == reg_NL3) && (5 == ((inst >> 21) & 0x1f))
183 && (4 == ((inst >> 1) & 0x3ff))) {
184 /*
185 * We got the instruction. Now, look back to make sure it was
186 * proceeded by what we expected. 2 instructions back should be
187 * an ADD or ADDI instruction.
188 */
189 unsigned int add_inst;
190
191 add_inst = pc[-2];
192 #if 0
193 fprintf(stderr, " add inst at %p: inst = 0x%08x\n",
194 pc - 2, add_inst);
195 #endif
196 opcode = add_inst >> 26;
197 if ((opcode == 31) && (266 == ((add_inst >> 1) & 0x1ff))) {
198 return 1;
199 } else if ((opcode == 14)) {
200 return 1;
201 } else {
202 fprintf(stderr,
203 "Whoa! Got allocation trap not preceeded by an ADD or ADDI instruction: 0x%08x\n",
204 add_inst);
205 }
206 }
207 return 0;
208 }
209
210 /*
211 * Use this function to enable the minimum number of signals we need
212 * when our trap handler needs to call Lisp code that might cons. For
213 * consing to work with gencgc, we need to be able to trap the SIGILL
214 * signal to perform allocation.
215 */
216 void
217 enable_some_signals(void)
218 {
219 sigset_t sigs;
220
221 #if 0
222 fprintf(stderr, "Enabling some signals\n");
223 #endif
224 #if 0
225 sigprocmask(SIG_SETMASK, &context->uc_sigmask, 0);
226 #else
227 sigemptyset(&sigs);
228 sigaddset(&sigs, SIGILL);
229 sigaddset(&sigs, SIGBUS);
230 sigprocmask(SIG_UNBLOCK, &sigs, NULL);
231 #endif
232 #if 0
233 fprintf(stderr, "Some signals enabled\n");
234 #endif
235 }
236
237 void
238 handle_allocation_trap(os_context_t * context)
239 {
240 unsigned int *pc;
241 unsigned int inst;
242 unsigned int or_inst;
243 unsigned int target;
244 unsigned int opcode;
245 int size;
246 int immed;
247 boolean were_in_lisp;
248 char *memory;
249 sigset_t block;
250
251 target = 0;
252 size = 0;
253
254 #if 0
255 fprintf(stderr, "In handle_allocation_trap\n");
256 #endif
257 /*
258 * I don't think it's possible for us NOT to be in lisp when we get
259 * here. Remove this later?
260 */
261 were_in_lisp = !foreign_function_call_active;
262
263 if (were_in_lisp) {
264 fake_foreign_function_call(context);
265 } else {
266 fprintf(stderr, "**** Whoa! allocation trap and we weren't in lisp!\n");
267 }
268
269 /*
270 * Look at current instruction: TWNE temp, NL3. We're here because
271 * temp > NL3 and temp is the end of the allocation, and NL3 is
272 * current-region-end-addr.
273 *
274 * We need to adjust temp and alloc-tn.
275 */
276
277 pc = (unsigned int *) SC_PC(context);
278 inst = pc[0];
279 target = (inst >> 16) & 0x1f;
280
281 #if 0
282 fprintf(stderr, "handle_allocation_trap at %p:\n", pc);
283 fprintf(stderr, " trap inst = 0x%08x\n", inst);
284 fprintf(stderr, " target reg = %s\n", lisp_register_names[target]);
285 #endif
286 /*
287 * Go back and look at the add/addi instruction. The second src arg
288 * is the size of the allocation. Get it and call alloc to allocate
289 * new space.
290 */
291 inst = pc[-2];
292 opcode = inst >> 26;
293 #if 0
294 fprintf(stderr, " add inst = 0x%08x, opcode = %d\n", inst, opcode);
295 #endif
296 if (opcode == 14) {
297 /*
298 * ADDI temp-tn, alloc-tn, size
299 *
300 * Extract the size
301 */
302 size = (inst & 0xffff);
303 } else if (opcode == 31) {
304 /*
305 * ADD temp-tn, alloc-tn, size-tn
306 *
307 * Extract the size
308 */
309 int reg;
310
311 reg = (inst >> 11) & 0x1f;
312 #if 0
313 fprintf(stderr, " add, reg = %s\n", lisp_register_names[reg]);
314 #endif
315 size = SC_REG(context, reg);
316 }
317 #if 0
318 fprintf(stderr, "Alloc %d to %s\n", size, lisp_register_names[target]);
319 #endif
320
321 /*
322 * Well, maybe not. sigill_handler probably shouldn't be unblocking
323 * all signals. So, let's enable just the signals we need. Since
324 * alloc might call GC, we need to have SIGILL enabled so we can do
325 * allocation. Do we need more?
326 */
327 enable_some_signals();
328
329 #if 0
330 fprintf(stderr, "Ready to alloc\n");
331 fprintf(stderr, "free_pointer = 0x%08x\n",
332 current_dynamic_space_free_pointer);
333 #endif
334 /*
335 * alloc-tn was incremented by size. Need to decrement it by size to restore it's original value.
336 */
337 current_dynamic_space_free_pointer =
338 (lispobj *) ((long) current_dynamic_space_free_pointer - size);
339 #if 0
340 fprintf(stderr, "free_pointer = 0x%08x new\n",
341 current_dynamic_space_free_pointer);
342 #endif
343
344 memory = (char *) alloc(size);
345
346 #if 0
347 fprintf(stderr, "alloc returned %p\n", memory);
348 fprintf(stderr, "free_pointer = 0x%08x\n",
349 current_dynamic_space_free_pointer);
350 #endif
351
352 /*
353 * The allocation macro wants the result to point to the end of the
354 * object!
355 */
356 memory += size;
357 #if 0
358 fprintf(stderr, "object end at %p\n", memory);
359 #endif
360 SC_REG(context, target) = (unsigned long) memory;
361 SC_REG(context, reg_ALLOC) =
362 (unsigned long) current_dynamic_space_free_pointer;
363
364 if (were_in_lisp) {
365 undo_fake_foreign_function_call(context);
366 }
367
368
369 }
370 #endif
371
372 static void
373 sigill_handler(HANDLER_ARGS)
374 {
375 int badinst;
376 int opcode;
377
378 sigprocmask(SIG_SETMASK, &context->uc_sigmask, 0);
379 opcode = *((int *) SC_PC(context));
380
381 #if 0
382 printf("SIGILL entry: opcode = 0x%08x\n", opcode);
383 fflush(stdout);
384 #endif
385
386 if (opcode == ((3 << 26) | (0x18 << 21) | (reg_NL3 << 16))) {
387 /* Got a twnei reg_NL3,0 - check for deferred interrupt */
388 #if 1
389 /* Clear the pseudo-atomic-interrupted bit */
390 SC_REG(context, reg_ALLOC) &= ~1;
391 #else
392 (SC_REG(context, reg_ALLOC) -= PSEUDO_ATOMIC_INTERRUPTED_BIAS);
393 #endif
394 arch_skip_instruction(context);
395 interrupt_handle_pending(context);
396 #ifdef DARWIN
397 /* Work around G5 bug; fix courtesy gbyers via chandler */
398 sigreturn(context);
399 #endif
400 return;
401 }
402
403 /* Is this an allocation trap? */
404 #ifdef GENCGC
405 if (allocation_trap_p(context)) {
406 handle_allocation_trap(context);
407 arch_skip_instruction(context);
408 #ifdef DARWIN
409 sigreturn(context);
410 #endif
411 return;
412 }
413 #endif
414
415 if ((opcode >> 16) == ((3 << 10) | (6 << 5))) {
416 /* twllei reg_ZERO,N will always trap if reg_ZERO = 0 */
417 int trap = opcode & 0x1f, extra = (opcode >> 5) & 0x1f;
418
419 #if 0
420 printf("SIGILL: TWLLEI, code = %d\n", trap);
421 fflush(stdout);
422 #endif
423
424 switch (trap) {
425 case trap_Halt:
426 fake_foreign_function_call(context);
427 lose("%%primitive halt called; the party is over.\n");
428
429 case trap_Error:
430 case trap_Cerror:
431 interrupt_internal_error(signal, code, context,
432 trap == trap_Cerror);
433 break;
434
435 case trap_PendingInterrupt:
436 arch_skip_instruction(context);
437 interrupt_handle_pending(context);
438 break;
439
440 case trap_Breakpoint:
441 #if 0
442 printf("trap_Breakpoint\n");
443 fflush(stdout);
444 #endif
445 handle_breakpoint(signal, code, context);
446 break;
447
448 case trap_FunctionEndBreakpoint:
449 #if 0
450 printf("trap_FunctionEndBreakpoint\n");
451 fflush(stdout);
452 #endif
453 SC_PC(context) =
454 (int) handle_function_end_breakpoint(signal, code, context);
455 break;
456
457 case trap_AfterBreakpoint:
458 #if 0
459 fprintf(stderr, "trap_AfterBreakpoint: break_addr = %p\n",
460 skipped_break_addr);
461 fprintf(stderr, " CSP = %p\n",
462 (void *) SC_REG(context, reg_CSP));
463 fprintf(stderr, " CFP = %p\n",
464 (void *) SC_REG(context, reg_CFP));
465 fprintf(stderr, " OCFP = %p\n",
466 (void *) SC_REG(context, reg_OCFP));
467 #endif
468 /* Put our breakpoint instruction back in */
469 *skipped_break_addr = TWLLEI_R0(trap_Breakpoint);
470 skipped_break_addr = NULL;
471 *(unsigned long *) SC_PC(context) = displaced_after_inst;
472 context->uc_sigmask = orig_sigmask;
473
474 os_flush_icache((os_vm_address_t) SC_PC(context),
475 sizeof(unsigned long));
476 break;
477
478 default:
479 interrupt_handle_now(signal, code, context);
480 break;
481 }
482 #ifdef DARWIN
483 /* Work around G5 bug; fix courtesy gbyers via chandler */
484 sigreturn(context);
485 #endif
486 return;
487 }
488 if (((opcode >> 26) == 3) && (((opcode >> 21) & 31) == 24)) {
489 interrupt_internal_error(signal, code, context, 0);
490 #ifdef DARWIN
491 /* Work around G5 bug; fix courtesy gbyers via chandler */
492 sigreturn(context);
493 #endif
494 return;
495 }
496
497 interrupt_handle_now(signal, code, context);
498 #ifdef DARWIN
499 /* Work around G5 bug; fix courtesy gbyers via chandler */
500 sigreturn(context);
501 #endif
502 }
503
504
505 void
506 arch_install_interrupt_handlers(void)
507 {
508 interrupt_install_low_level_handler(SIGILL, sigill_handler);
509 interrupt_install_low_level_handler(SIGTRAP, sigill_handler);
510 }
511
512
513 extern lispobj call_into_lisp(lispobj fun, lispobj * args, int nargs);
514
515 lispobj
516 funcall0(lispobj function)
517 {
518 lispobj *args = current_control_stack_pointer;
519
520 return call_into_lisp(function, args, 0);
521 }
522
523 lispobj
524 funcall1(lispobj function, lispobj arg0)
525 {
526 lispobj *args = current_control_stack_pointer;
527
528 current_control_stack_pointer += 1;
529 args[0] = arg0;
530
531 return call_into_lisp(function, args, 1);
532 }
533
534 lispobj
535 funcall2(lispobj function, lispobj arg0, lispobj arg1)
536 {
537 lispobj *args = current_control_stack_pointer;
538
539 current_control_stack_pointer += 2;
540 args[0] = arg0;
541 args[1] = arg1;
542
543 return call_into_lisp(function, args, 2);
544 }
545
546 lispobj
547 funcall3(lispobj function, lispobj arg0, lispobj arg1, lispobj arg2)
548 {
549 lispobj *args = current_control_stack_pointer;
550
551 current_control_stack_pointer += 3;
552 args[0] = arg0;
553 args[1] = arg1;
554 args[2] = arg2;
555
556 return call_into_lisp(function, args, 3);
557 }
558
559 void
560 ppc_flush_icache(os_vm_address_t address, os_vm_size_t length)
561 {
562 os_vm_address_t end =
563 (os_vm_address_t) ((int) (address + length + (32 - 1)) & ~(32 - 1));
564 extern void ppc_flush_cache_line(os_vm_address_t);
565
566 while (address < end) {
567 ppc_flush_cache_line(address);
568 address += 32;
569 }
570 }
571
572 #ifdef LINKAGE_TABLE
573 /* Linkage tables for PowerPC
574 *
575 * Linkage entry size is 16, because we need at least 4 instructions to
576 * implement a jump.
577 */
578
579 /*
580 * This had better match lisp::target-foreign-linkage-entry-size in
581 * ppco/parms.lisp! Each entry is 6 instructions long, so at least
582 * 24 bytes.
583 */
584 #ifndef LinkageEntrySize
585 #define LinkageEntrySize (8*4)
586 #endif
587
588 /*
589 * Define the registers to use in the linkage jump table. Can be the
590 * same. Some care must be exercised when choosing these. It has to be
591 * a register that is not otherwise being used. reg_NFP is a good
592 * choice. call_into_c trashes reg_NFP without preserving it, so we can
593 * trash it in the linkage jump table.
594 */
595 #define LINKAGE_TEMP_REG reg_NFP
596 #define LINKAGE_ADDR_REG reg_A0
597
598 /*
599 * Insert the necessary jump instructions at the given address.
600 */
601 void
602 arch_make_jump_entry(void *reloc_addr, void *target_addr)
603 {
604 /*
605 * Make JMP to function entry.
606 *
607 * The instruction sequence is:
608 *
609 * addis temp, 0, (hi part of reloc)
610 * ori temp, temp, (lo part of reloc)
611 * addis addr, 0, (hi part of addr)
612 * ori addr, addr, (low part of addr)
613 * mtctr addr
614 * bctr
615 *
616 */
617 int *inst_ptr;
618 unsigned long hi; /* Top 16 bits of address */
619 unsigned long lo; /* Low 16 bits of address */
620 unsigned int inst;
621
622 inst_ptr = (int *) reloc_addr;
623
624 /*
625 * Split the target address into hi and lo parts for the addis/ori
626 * instructions.
627 */
628 hi = (unsigned long) reloc_addr;
629 lo = hi & 0xffff;
630 hi >>= 16;
631
632 /*
633 * addis 3, 0, (hi part)
634 */
635 inst = (15 << 26) | (LINKAGE_ADDR_REG << 21) | (0 << 16) | hi;
636 *inst_ptr++ = inst;
637
638 /*
639 * ori 3, 3, (lo part)
640 */
641
642 inst =
643 (24 << 26) | (LINKAGE_ADDR_REG << 21) | (LINKAGE_ADDR_REG << 16) | lo;
644 *inst_ptr++ = inst;
645
646 /*
647 * Split the target address into hi and lo parts for the addis/ori
648 * instructions.
649 */
650
651 hi = (unsigned long) target_addr;
652 lo = hi & 0xffff;
653 hi >>= 16;
654
655 /*
656 * addis 13, 0, (hi part)
657 */
658
659 inst = (15 << 26) | (LINKAGE_TEMP_REG << 21) | (0 << 16) | hi;
660 *inst_ptr++ = inst;
661
662 /*
663 * ori 13, 13, (lo part)
664 */
665
666 inst =
667 (24 << 26) | (LINKAGE_TEMP_REG << 21) | (LINKAGE_TEMP_REG << 16) | lo;
668 *inst_ptr++ = inst;
669
670 /*
671 * mtctr 13
672 */
673
674 inst = (31 << 26) | (LINKAGE_TEMP_REG << 21) | (9 << 16) | (467 << 1);
675 *inst_ptr++ = inst;
676
677 /*
678 * bctr
679 */
680
681 inst = (19 << 26) | (20 << 21) | (528 << 1);
682 *inst_ptr++ = inst;
683
684
685 *inst_ptr++ = inst;
686
687 os_flush_icache((os_vm_address_t) reloc_addr,
688 (char *) inst_ptr - (char *) reloc_addr);
689 }
690
691 void
692 arch_make_linkage_entry(long linkage_entry, void *target_addr, long type)
693 {
694 int *reloc_addr = (int *) (FOREIGN_LINKAGE_SPACE_START
695
696 + linkage_entry * LinkageEntrySize);
697
698 if (type == 1) { /* code reference */
699 arch_make_jump_entry(reloc_addr, target_addr);
700 } else if (type == 2) {
701 *(unsigned long *) reloc_addr = (unsigned long) target_addr;
702 }
703 }
704
705 /* Make a the entry a jump to resolve_linkage_tramp. */
706
707 extern void resolve_linkage_tramp(void);
708
709 void
710 arch_make_lazy_linkage(long linkage_entry)
711 {
712 arch_make_linkage_entry(linkage_entry, (void *) resolve_linkage_tramp, 1);
713 }
714
715 /* Get linkage entry. We're given the return address which should be
716 the address of the jmpl instruction (2nd word) of the linkage
717 entry. Figure out which entry this address belong to. */
718
719 long
720 arch_linkage_entry(unsigned long retaddr)
721 {
722 return (retaddr - (FOREIGN_LINKAGE_SPACE_START))
723 / LinkageEntrySize;
724 }
725 #endif
726
727 int ieee754_rem_pio2(double x, double *y0, double *y1)
728 {
729 extern int __ieee754_rem_pio2(double x, double *y);
730
731 double y[2];
732 int n;
733
734 n = __ieee754_rem_pio2(x, y);
735 *y0 = y[0];
736 *y1 = y[1];
737
738 return n;
739 }

  ViewVC Help
Powered by ViewVC 1.1.5