From b008867d8189907a547d5a2f6b771557df4ca615 Mon Sep 17 00:00:00 2001 From: Ruben Ayrapetyan Date: Mon, 15 Jun 2015 14:40:56 +0300 Subject: [PATCH] Implement setjmp / longjmp in jerry-libc. JerryScript-DCO-1.0-Signed-off-by: Ruben Ayrapetyan r.ayrapetyan@samsung.com --- build/configs/toolchain_linux_armv7l-hf.cmake | 7 +- jerry-libc/arch/arm-v7.h | 77 +++++++++++- jerry-libc/arch/x86-32.h | 104 ++++++++++++++++ jerry-libc/arch/x86-64.h | 112 +++++++++++++++++- jerry-libc/include/setjmp.h | 60 ++++++++++ jerry-libc/target/linux/jerry-asm.S | 30 +++++ tests/unit/test-common.h | 1 + tests/unit/test-longjmp.cpp | 82 +++++++++++++ 8 files changed, 470 insertions(+), 3 deletions(-) create mode 100644 jerry-libc/include/setjmp.h create mode 100644 tests/unit/test-longjmp.cpp diff --git a/build/configs/toolchain_linux_armv7l-hf.cmake b/build/configs/toolchain_linux_armv7l-hf.cmake index 9f7405d93..4b688a85f 100644 --- a/build/configs/toolchain_linux_armv7l-hf.cmake +++ b/build/configs/toolchain_linux_armv7l-hf.cmake @@ -18,4 +18,9 @@ set(CMAKE_SYSTEM_PROCESSOR armv7l) set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) -set(FLAGS_COMMON_ARCH -mlittle-endian -mthumb) +# +# Limit fpu to VFPv3 with d0-d15 registers +# +# If this is changed, setjmp / longjmp for ARMv7 should be updated accordingly +# +set(FLAGS_COMMON_ARCH -mlittle-endian -mthumb -mfpu=vfpv3-d16) diff --git a/jerry-libc/arch/arm-v7.h b/jerry-libc/arch/arm-v7.h index c1ae263ee..508f27164 100644 --- a/jerry-libc/arch/arm-v7.h +++ b/jerry-libc/arch/arm-v7.h @@ -80,13 +80,88 @@ \ pop {r4-r12, pc}; +/* + * ldr argc ([sp + 0x0]) -> r0 + * add argv (sp + 0x4) -> r1 + * + * bl main + * + * bl exit + * + * infinite loop + */ #define _START \ ldr r0, [sp, #0]; \ add r1, sp, #4; \ bl main; \ \ - bl exit; \ + bl exit; \ 1: \ b 1b + +/* + * setjmp + * + * According to procedure call standard for the ARM architecture, the following + * registers are callee-saved, and so need to be stored in context: + * - r4 - r11 + * - sp + * - s16-s31 + * + * Also, we should store: + * - lr + * + * stmia {r4-r11, sp, lr} -> jmp_buf_0 (r0)! + * + * FIXME: + * vstm should not be performed in softfp mode + * vstm {s16-s31} -> jmp_buf_32 (r0)! + * + * mov r0, #0 + * + * bx lr + */ +#define _SETJMP \ + stmia r0!, {r4 - r11, sp, lr}; \ + \ + vstm r0!, {s16 - s31}; \ + \ + mov r0, #0; \ + \ + bx lr; + +/* + * longjmp + * + * See also: + * _SETJMP + * + * ldmia jmp_buf_0 (r0)! -> {r4-r11, sp, lr} + * + * FIXME: + * vstm should not be performed in softfp mode + * vldm jmp_buf_32 (r0)! -> {s16-s31} + * + * mov r1 -> r0 + * cmp r0, #0 + * bne 1f + * mov #1 -> r0 + * 1: + * + * bx lr + */ +#define _LONGJMP \ + ldmia r0!, {r4 - r11, sp, lr}; \ + \ + vldm r0!, {s16 - s31}; \ + \ + mov r0, r1; \ + cmp r0, #0; \ + bne 1f; \ + mov r0, #1; \ + 1: \ + \ + bx lr; + #endif /* !ASM_ARM_H */ diff --git a/jerry-libc/arch/x86-32.h b/jerry-libc/arch/x86-32.h index 2370a3904..e1538107c 100644 --- a/jerry-libc/arch/x86-32.h +++ b/jerry-libc/arch/x86-32.h @@ -92,6 +92,17 @@ pop %edi; \ ret; +/* + * push argv (%esp + 4) + * push argc ([%esp + 0x4]) + * + * call main + * + * push main_ret (%eax) + * call exit + * + * infinite loop + */ #define _START \ mov %esp, %eax; \ add $4, %eax; \ @@ -103,7 +114,100 @@ \ push %eax; \ call exit; \ + \ 1: \ jmp 1b +/* + * setjmp + * + * According to x86_32 System V ABI, the following registers are + * callee-saved, and so need to be stored in context: + * - %ebx + * - %esp + * - %ebp + * - %esi + * - %edi + * - x87 control word + * + * Also, we should store: + * - return address (to jump to upon longjmp) + * + * mov return_address ([%esp]) -> %eax + * + * mov env ([%esp + 0x4]) -> %edx + * + * mov %ebx -> jmp_buf_0 ([%edx + 0x0]) + * mov %esp -> jmp_buf_4 ([%edx + 0x4]) + * mov %ebp -> jmp_buf_8 ([%edx + 0x8]) + * mov %esi -> jmp_buf_12 ([%edx + 0xc]) + * mov %edi -> jmp_buf_16 ([%edx + 0x10]) + * mov %eax -> jmp_buf_20 ([%edx + 0x14]) + * fnstcw -> jmp_buf_24 ([%edx + 0x18]) + * + * ret + */ +#define _SETJMP \ + mov (%esp), %eax; \ + mov 0x4 (%esp), %edx; \ + \ + mov %ebx, 0x00 (%edx); \ + mov %esp, 0x04 (%edx); \ + mov %ebp, 0x08 (%edx); \ + mov %esi, 0x0c (%edx); \ + mov %edi, 0x10 (%edx); \ + mov %eax, 0x14 (%edx); \ + fnstcw 0x18 (%edx); \ + \ + xor %eax, %eax; \ + \ + ret + +/* + * longjmp + * + * See also: + * _SETJMP + * + * mov env ([%esp + 0x4]) -> %edx + * mov val ([%esp + 0x8]) -> %eax + * + * mov jmp_buf_0 ([%edx + 0x0]) -> %ebx + * mov jmp_buf_4 ([%edx + 0x8]) -> %esp + * mov jmp_buf_8 ([%edx + 0x10]) -> %ebp + * mov jmp_buf_12 ([%edx + 0x18]) -> %esi + * mov jmp_buf_16 ([%edx + 0x20]) -> %edi + * mov jmp_buf_20 ([%edx + 0x28]) -> %ecx + * fldcw jmp_buf_24 ([%edx + 0x30]) + * + * mov return_address (%ecx) -> ([%esp]) + * + * cmp (%eax), 0x0 + * jnz 1f + * xor %eax, %eax + * 1: + * + * ret + */ +#define _LONGJMP \ + mov 0x4 (%esp), %edx; \ + mov 0x8 (%esp), %eax; \ + \ + mov 0x0 (%edx), %ebx; \ + mov 0x4 (%edx), %esp; \ + mov 0x8 (%edx), %ebp; \ + mov 0xc (%edx), %esi; \ + mov 0x10 (%edx), %edi; \ + mov 0x14 (%edx), %ecx; \ + fldcw 0x18 (%edx); \ + \ + mov %ecx, (%esp); \ + \ + test %eax, %eax; \ + jnz 1f; \ + xor %eax, %eax; \ + 1: \ + \ + ret + #endif /* !ASM_X86_H */ diff --git a/jerry-libc/arch/x86-64.h b/jerry-libc/arch/x86-64.h index e709199f8..5e53da604 100644 --- a/jerry-libc/arch/x86-64.h +++ b/jerry-libc/arch/x86-64.h @@ -64,6 +64,17 @@ syscall; \ ret; +/* + * mov argc ([%rsp]) -> %rdi + * mov argv (%rsp + 0x8) -> %rsi + * + * call main + * + * mov main_ret (%rax) -> %rdi + * call exit + * + * infinite loop + */ #define _START \ mov (%rsp), %rdi; \ mov %rsp, %rsi; \ @@ -71,8 +82,107 @@ callq main; \ \ mov %rax, %rdi; \ - callq exit; \ + callq exit; \ 1: \ jmp 1b +/* + * setjmp + * + * According to x86_64 System V ABI, the following registers are + * callee-saved, and so need to be stored in context: + * - %rbp + * - %rbx + * - %r12 + * - %r13 + * - %r14 + * - %r15 + * - x87 control word + * + * Also, we should store: + * - %rsp (stack pointer) + * - return address (to jump to upon longjmp) + * + * mov return_address ([%rsp]) -> %rax + * + * mov %rsp -> jmp_buf_0 ([%rdi + 0x0]) + * mov %rax -> jmp_buf_8 ([%rdi + 0x8]) + * mov %rbp -> jmp_buf_16 ([%rdi + 0x10]) + * mov %rbx -> jmp_buf_24 ([%rdi + 0x18]) + * mov %r12 -> jmp_buf_32 ([%rdi + 0x20]) + * mov %r13 -> jmp_buf_40 ([%rdi + 0x28]) + * mov %r14 -> jmp_buf_48 ([%rdi + 0x30]) + * mov %r15 -> jmp_buf_56 ([%rdi + 0x38]) + * fnstcw -> jmp_buf_64 ([%rdi + 0x40]) + * + * ret + */ +#define _SETJMP \ + mov (%rsp), %rax; \ + \ + mov %rsp, 0x00(%rdi); \ + mov %rax, 0x08(%rdi); \ + mov %rbp, 0x10(%rdi); \ + mov %rbx, 0x18(%rdi); \ + mov %r12, 0x20(%rdi); \ + mov %r13, 0x28(%rdi); \ + mov %r14, 0x30(%rdi); \ + mov %r15, 0x38(%rdi); \ + fnstcw 0x40(%rdi); \ + \ + xor %rax, %rax; \ + \ + ret; + +/* + * longjmp + * + * See also: + * _SETJMP + * + * mov jmp_buf_0 ([%rdi + 0x0]) -> %rsp + * mov jmp_buf_8 ([%rdi + 0x8]) -> %rax + * mov jmp_buf_16 ([%rdi + 0x10]) -> %rbp + * mov jmp_buf_24 ([%rdi + 0x18]) -> %rbx + * mov jmp_buf_32 ([%rdi + 0x20]) -> %r12 + * mov jmp_buf_40 ([%rdi + 0x28]) -> %r13 + * mov jmp_buf_48 ([%rdi + 0x30]) -> %r14 + * mov jmp_buf_56 ([%rdi + 0x38]) -> %r15 + * fldcw jmp_buf_64 ([%rdi + 0x40]) + * + * mov return_address (%rax) -> ([%rsp]) + * + * mov val (%rsi) -> %rax + * + * test (%rax), (%rax) + * jnz 1f + * mov $1, %rax + * 1: + * + * ret + */ +#define _LONGJMP \ + mov 0x00(%rdi), %rsp; \ + mov 0x08(%rdi), %rax; \ + mov 0x10(%rdi), %rbp; \ + mov 0x18(%rdi), %rbx; \ + mov 0x20(%rdi), %r12; \ + mov 0x28(%rdi), %r13; \ + mov 0x30(%rdi), %r14; \ + mov 0x38(%rdi), %r15; \ + fldcw 0x40(%rdi); \ + \ + mov %rax, (%rsp); \ + \ + mov %rsi, %rax; \ + \ + test %rax, %rax; \ + jnz 1f; \ + mov $1, %rax; \ + 1: \ + \ + ret + + + #endif /* !ASM_X64_H */ diff --git a/jerry-libc/include/setjmp.h b/jerry-libc/include/setjmp.h new file mode 100644 index 000000000..89071eb9e --- /dev/null +++ b/jerry-libc/include/setjmp.h @@ -0,0 +1,60 @@ +/* Copyright 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JERRY_LIBC_SETJMP_H +#define JERRY_LIBC_SETJMP_H + +#ifdef __cplusplus +# define EXTERN_C "C" +#else /* !__cplusplus */ +# define EXTERN_C +#endif /* !__cplusplus */ + +/** + * Storage for context, used for nonlocal goto + * + * x86_64 (8 * 8 + 2 bytes): + * 0x00 - %rsp + * 0x08 - return address + * 0x10 - %rbp + * 0x18 - %rbx + * 0x20 - %r12 + * 0x28 - %r13 + * 0x30 - %r14 + * 0x38 - %r15 + * 0x40 - x87 control word + * + * x86_32 (6 * 4 + 2 bytes): + * - %ebx + * - %esp + * - %ebp + * - %esi + * - %edi + * - return address (to jump to upon longjmp) + * - x87 control word + * + * ARMv7 (8 * 4 + 16 * 4 bytes): + * - r4 - r11 + * - s16 - s31 (if hardfp enabled) + * + * See also: + * setjmp, longjmp + */ +typedef uint64_t jmp_buf[12]; + +extern EXTERN_C int setjmp (jmp_buf env); +extern EXTERN_C void longjmp (jmp_buf env, int val); + +#endif /* !JERRY_LIBC_SETJMP_H */ diff --git a/jerry-libc/target/linux/jerry-asm.S b/jerry-libc/target/linux/jerry-asm.S index 9c3eea19b..93a00cb85 100644 --- a/jerry-libc/target/linux/jerry-asm.S +++ b/jerry-libc/target/linux/jerry-asm.S @@ -38,3 +38,33 @@ syscall_3_asm: SYSCALL_3 .size syscall_3_asm, . - syscall_3_asm +/** + * setjmp (jmp_buf env) + * + * See also: + * longjmp + * + * @return 0 - if returns from direct call, + * nonzero - if returns after longjmp. + */ +.global setjmp +.type setjmp, %function +setjmp: + _SETJMP +.size setjmp, . - setjmp + +/** + * longjmp (jmp_buf env, int val) + * + * Note: + * if val is not 0, then it would be returned from setjmp, + * otherwise - 0 would be returned. + * + * See also: + * setjmp + */ +.global longjmp +.type longjmp, %function +longjmp: + _LONGJMP +.size longjmu, . - setjmp diff --git a/tests/unit/test-common.h b/tests/unit/test-common.h index b52c6f59e..5d1390797 100644 --- a/tests/unit/test-common.h +++ b/tests/unit/test-common.h @@ -19,6 +19,7 @@ #include "jrt.h" #include +#include #include #include #include diff --git a/tests/unit/test-longjmp.cpp b/tests/unit/test-longjmp.cpp new file mode 100644 index 000000000..a9ad08ab3 --- /dev/null +++ b/tests/unit/test-longjmp.cpp @@ -0,0 +1,82 @@ +/* Copyright 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test-common.h" + +#define TEST_MAX_DEPTH 10 +#define TEST_ITERATIONS_NUM 256 + +jmp_buf buffers[TEST_MAX_DEPTH]; + +static void +test_setjmp_longjmp (volatile int depth) +{ + if (depth != TEST_MAX_DEPTH) + { + int a = 1, b = 2, c = 3; + + int array[256]; + for (int i = 0; i < 256; i++) + { + array[i] = i; + } + + (void) a; + (void) b; + (void) c; + (void) array; + + int k = setjmp (buffers[depth]); + + if (k == 0) + { + test_setjmp_longjmp (depth + 1); + } + else + { + JERRY_ASSERT (k == depth + 1); + + JERRY_ASSERT (a == 1); + JERRY_ASSERT (b == 2); + JERRY_ASSERT (c == 3); + + for (int i = 0; i < 256; i++) + { + JERRY_ASSERT (array[i] == i); + } + } + } + else + { + int t = rand () % depth; + JERRY_ASSERT (t >= 0 && t < depth); + + longjmp (buffers[t], t + 1); + } +} + +int +main (int __attr_unused___ argc, + char __attr_unused___ **argv) +{ + TEST_INIT (); + + for (int i = 0; i < TEST_ITERATIONS_NUM; i++) + { + test_setjmp_longjmp (0); + } + + return 0; +} /* main */