diff --git a/src/bytecodes.h b/src/bytecodes.h index 52703d0de..0e1044541 100644 --- a/src/bytecodes.h +++ b/src/bytecodes.h @@ -52,7 +52,6 @@ namespace toit { FORMAT(OP_BC, 2) \ FORMAT(OP_BG, 2) \ FORMAT(OP_BF, 2) \ - FORMAT(OP_BB, 2) \ FORMAT(OP_BCI, 2) \ FORMAT(OP_BII, 2) \ FORMAT(OP_BLC, 2) \ @@ -74,11 +73,10 @@ namespace toit { FORMAT(OP_SS_SO, 5) \ FORMAT(OP_SCI, 3) \ FORMAT(OP_SII, 3) \ - FORMAT(OP_SB, 3) \ + FORMAT(OP_SB_SB, 5) \ FORMAT(OP_SU_SU, 5) \ - // Format Toit bytecodes enum BytecodeFormat { #define THE_FORMAT(format, length) format, @@ -180,12 +178,9 @@ enum BytecodeFormat { BYTECODE(BRANCH, 3, OP_SF, "branch") \ BYTECODE(BRANCH_IF_TRUE, 3, OP_SF, "branch if true") \ BYTECODE(BRANCH_IF_FALSE, 3, OP_SF, "branch if false") \ - BYTECODE(BRANCH_BACK, 2, OP_BB, "branch back") \ - BYTECODE(BRANCH_BACK_WIDE, 3, OP_SB, "branch back wide") \ - BYTECODE(BRANCH_BACK_IF_TRUE, 2, OP_BB, "branch back if true") \ - BYTECODE(BRANCH_BACK_IF_TRUE_WIDE, 3, OP_SB, "branch back if true wide") \ - BYTECODE(BRANCH_BACK_IF_FALSE, 2, OP_BB, "branch back if false") \ - BYTECODE(BRANCH_BACK_IF_FALSE_WIDE, 3, OP_SB, "branch back if false wide") \ + BYTECODE(BRANCH_BACK, 5, OP_SB_SB, "branch back") \ + BYTECODE(BRANCH_BACK_IF_TRUE, 5, OP_SB_SB, "branch back if true") \ + BYTECODE(BRANCH_BACK_IF_FALSE, 5, OP_SB_SB, "branch back if false") \ BYTECODE(PRIMITIVE, 4, OP_BU_SU, "invoke primitive") \ BYTECODE(THROW, 2, OP_BU, "throw") \ BYTECODE(RETURN, 3, OP_BS_BU, "return") \ diff --git a/src/compiler/emitter.cc b/src/compiler/emitter.cc index 0e064668f..75bc6278e 100644 --- a/src/compiler/emitter.cc +++ b/src/compiler/emitter.cc @@ -454,7 +454,9 @@ void Emitter::branch(Condition condition, Label* label) { if (label->is_bound()) { int offset = -(label->position() - position); ASSERT(offset >= 0); - emit_possibly_wide(op, offset); + emit_opcode(op); + emit_uint16(offset); + emit_uint16(position); } else { label->use(position, height()); emit_opcode(op); diff --git a/src/encoder.cc b/src/encoder.cc index 542ff89f6..f8a101ddb 100644 --- a/src/encoder.cc +++ b/src/encoder.cc @@ -264,12 +264,10 @@ bool ProgramOrientedEncoder::encode_error(Object* type, const char* message, Sta return !buffer()->has_overflow(); } -#ifdef PROFILER bool ProgramOrientedEncoder::encode_profile(Profiler* profiler, String* title, int cutoff) { profiler->encode_on(this, title, cutoff); return !buffer()->has_overflow(); } -#endif void Encoder::write_byte(uint8 c) { _buffer->put_byte(c); diff --git a/src/encoder.h b/src/encoder.h index ddac6cfcc..9c0265d42 100644 --- a/src/encoder.h +++ b/src/encoder.h @@ -133,9 +133,7 @@ class ProgramOrientedEncoder : public Encoder { bool encode_error(Object* type, Object* message, Stack* stack); bool encode_error(Object* type, const char* message, Stack* stack); -#ifdef PROFILER bool encode_profile(Profiler* profile, String* title, int cutoff); -#endif Program* program() { return _program; } diff --git a/src/event_sources/timer.cc b/src/event_sources/timer.cc index e06944f39..076f550cd 100644 --- a/src/event_sources/timer.cc +++ b/src/event_sources/timer.cc @@ -114,9 +114,11 @@ void TimerEventSource::entry() { break; } } - - int delay_ms = (delay_us + 1000 - 1) / 1000; // Ceiling division. - OS::wait(_timer_changed, delay_ms); + if (delay_us > 0) { + OS::wait_us(_timer_changed, delay_us); + } else { + OS::wait(_timer_changed); + } } } diff --git a/src/interpreter.cc b/src/interpreter.cc index 24059de7c..2e7e9cfdd 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -38,9 +38,6 @@ Interpreter::Interpreter() , _sp(null) , _try_sp(null) , _watermark(null) { -#ifdef PROFILER - _is_profiler_active = false; -#endif } void Interpreter::activate(Process* process) { @@ -55,20 +52,6 @@ void Interpreter::preempt() { _watermark = PREEMPTION_MARKER; } -#ifdef PROFILER -void Interpreter::profile_register_method(Method method) { - int absolute_bci = process()->program()->absolute_bci_from_bcp(method.header_bcp()); - Profiler* profiler = process()->profiler(); - if (profiler != null) profiler->register_method(absolute_bci); -} - -void Interpreter::profile_increment(uint8* bcp) { - int absolute_bci = process()->program()->absolute_bci_from_bcp(bcp); - Profiler* profiler = process()->profiler(); - if (profiler != null) profiler->increment(absolute_bci); -} -#endif - Method Interpreter::lookup_entry() { Method result = _process->entry(); if (!result.is_valid()) FATAL("Cannot locate entry method for interpreter"); @@ -79,9 +62,6 @@ Object** Interpreter::load_stack() { Stack* stack = _process->task()->stack(); GcMetadata::insert_into_remembered_set(stack); stack->transfer_to_interpreter(this); -#ifdef PROFILER - set_profiler_state(); -#endif Object** watermark = _watermark; Object** new_watermark = _limit + RESERVED_STACK_FOR_STACK_OVERFLOWS; while (true) { @@ -107,13 +87,6 @@ void Interpreter::store_stack(Object** sp) { } } -#ifdef PROFILER -void Interpreter::set_profiler_state() { - Profiler* profiler = process()->profiler(); - _is_profiler_active = profiler != null && profiler->should_profile_task(process()->task()->id()); -} -#endif - void Interpreter::prepare_task(Method entry, Instance* code) { push(code); static_assert(FRAME_SIZE == 2, "Unexpected frame size"); diff --git a/src/interpreter.h b/src/interpreter.h index 00a62f04d..36f262630 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -113,6 +113,7 @@ class Interpreter { void prepare_task(Method entry, Instance* code); void preempt(); + uint8* preemption_method_header_bcp() const { return _preemption_method_header_bcp; } private: Object** const PREEMPTION_MARKER = reinterpret_cast(UINTPTR_MAX); @@ -127,16 +128,12 @@ class Interpreter { // Stack overflow handling. std::atomic _watermark; + // Preemption method. + uint8* _preemption_method_header_bcp; + void trace(uint8* bcp); Method lookup_entry(); -#ifdef PROFILER - bool _is_profiler_active; - void profile_register_method(Method method); - void profile_increment(uint8* bcp); - void set_profiler_state(); -#endif - enum OverflowState { OVERFLOW_RESUME, OVERFLOW_PREEMPT, diff --git a/src/interpreter_run.cc b/src/interpreter_run.cc index 8fdab1e6a..02a5816c8 100644 --- a/src/interpreter_run.cc +++ b/src/interpreter_run.cc @@ -87,15 +87,8 @@ Method Program::find_method(Object* receiver, int offset) { // OPCODE_TRACE is only called from within Interpreter::run which gives access to: // uint8* bcp; -// uword index; -#ifdef PROFILER -#define OPCODE_TRACE() \ - if (_is_profiler_active) profile_increment(bcp); \ +#define OPCODE_TRACE() \ if (Flags::trace) trace(bcp); -#else -#define OPCODE_TRACE() \ - if (Flags::trace) trace(bcp); -#endif // Dispatching helper macros. #define DISPATCH(n) \ @@ -145,13 +138,6 @@ Method Program::find_method(Object* receiver, int offset) { #define B_ARG1(name) uint8 name = bcp[1]; #define S_ARG1(name) uint16 name = Utils::read_unaligned_uint16(bcp + 1); -#ifdef PROFILER -#define REGISTER_METHOD(target) \ - if (_is_profiler_active) profile_register_method(target); -#else -#define REGISTER_METHOD(target) -#endif - // CHECK_STACK_OVERFLOW checks if there is enough stack space to call // the given target method. #define CHECK_STACK_OVERFLOW(target) \ @@ -162,6 +148,7 @@ Method Program::find_method(Object* receiver, int offset) { case OVERFLOW_RESUME: \ break; \ case OVERFLOW_PREEMPT: \ + _preemption_method_header_bcp = target.header_bcp(); \ static_assert(FRAME_SIZE == 2, "Unexpected frame size"); \ PUSH(reinterpret_cast(target.entry())); \ PUSH(program->frame_marker()); \ @@ -173,13 +160,14 @@ Method Program::find_method(Object* receiver, int offset) { } // CHECK_PREEMPT checks for preemption and watchdog interrupts. -#define CHECK_PREEMPT() \ +#define CHECK_PREEMPT(entry) \ if (_watermark == PREEMPTION_MARKER) { \ OverflowState state; \ sp = handle_preempt(sp, &state); \ if (state == OVERFLOW_EXCEPTION) { \ goto THROW_IMPLEMENTATION; \ } \ + _preemption_method_header_bcp = Method::header_from_entry(entry); \ static_assert(FRAME_SIZE == 2, "Unexpected frame size"); \ PUSH(reinterpret_cast(bcp)); \ PUSH(program->frame_marker()); \ @@ -191,7 +179,6 @@ Method Program::find_method(Object* receiver, int offset) { static_assert(FRAME_SIZE == 2, "Unexpected frame size"); \ PUSH(reinterpret_cast(return_address)); \ PUSH(program->frame_marker()); \ - REGISTER_METHOD(target); \ CHECK_STACK_OVERFLOW(target) \ bcp = target.entry(); \ DISPATCH(0) @@ -270,6 +257,7 @@ Interpreter::Result Interpreter::run() { #undef LABEL // Interpretation state. + _preemption_method_header_bcp = null; Object** sp = load_stack(); Program* program = _process->program(); uword _index_ = 0; @@ -279,11 +267,6 @@ Interpreter::Result Interpreter::run() { ASSERT(frame_marker == program->frame_marker()); } uint8* bcp = reinterpret_cast(POP()); - -#ifdef PROFILER - set_profiler_state(); -#endif - DISPATCH(0); OPCODE_BEGIN_WITH_WIDE(LOAD_LOCAL, stack_offset); @@ -624,7 +607,7 @@ Interpreter::Result Interpreter::run() { } else { DROP(extra); } - CALL_METHOD(target, INVOKE_BLOCK_LENGTH) + CALL_METHOD(target, INVOKE_BLOCK_LENGTH); OPCODE_END(); OPCODE_BEGIN(INVOKE_INITIALIZER_TAIL); @@ -900,24 +883,27 @@ Interpreter::Result Interpreter::run() { } OPCODE_END(); - OPCODE_BEGIN_WITH_WIDE(BRANCH_BACK, relative_offset); - bcp -= relative_offset; - CHECK_PREEMPT(); + OPCODE_BEGIN(BRANCH_BACK); + uint8* entry = bcp - Utils::read_unaligned_uint16(bcp + 3); + bcp -= Utils::read_unaligned_uint16(bcp + 1); + CHECK_PREEMPT(entry); DISPATCH(0); OPCODE_END(); - OPCODE_BEGIN_WITH_WIDE(BRANCH_BACK_IF_TRUE, relative_offset); + OPCODE_BEGIN(BRANCH_BACK_IF_TRUE); if (is_true_value(program, POP())) { - bcp -= relative_offset; - CHECK_PREEMPT(); + uint8* entry = bcp - Utils::read_unaligned_uint16(bcp + 3); + bcp -= Utils::read_unaligned_uint16(bcp + 1); + CHECK_PREEMPT(entry); DISPATCH(0); } OPCODE_END(); - OPCODE_BEGIN_WITH_WIDE(BRANCH_BACK_IF_FALSE, relative_offset); + OPCODE_BEGIN(BRANCH_BACK_IF_FALSE); if (!is_true_value(program, POP())) { - bcp -= relative_offset; - CHECK_PREEMPT(); + uint8* entry = bcp - Utils::read_unaligned_uint16(bcp + 3); + bcp -= Utils::read_unaligned_uint16(bcp + 1); + CHECK_PREEMPT(entry); DISPATCH(0); } OPCODE_END(); diff --git a/src/objects.cc b/src/objects.cc index 8ffd68aa2..5eb1998f1 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -219,6 +219,17 @@ void Array::roots_do(RootCallback* cb) { cb->do_roots(_root_at(_offset_from(0)), length()); } +int Stack::absolute_bci_at_preemption(Program* program) { + // Check that the stack has both words. + if (_stack_sp_addr() + 1 >= _stack_base_addr()) return -1; + // Check that the frame marker is correct. + if (at(0) != program->frame_marker()) return -1; + // Get the bytecode pointer and convert it to an index. + uint8* bcp = reinterpret_cast(at(1)); + if (!program->bytecodes.is_inside(bcp)) return -1; + return program->absolute_bci_from_bcp(bcp); +} + void Stack::roots_do(Program* program, RootCallback* cb) { int top = this->top(); // Skip over pointers into the bytecodes. diff --git a/src/objects.h b/src/objects.h index 604a6c3ab..87e67891d 100644 --- a/src/objects.h +++ b/src/objects.h @@ -683,6 +683,8 @@ class Stack : public HeapObject { int top() { return _word_at(TOP_OFFSET); } int try_top() { return _word_at(TRY_TOP_OFFSET); } + int absolute_bci_at_preemption(Program* program); + void transfer_to_interpreter(Interpreter* interpreter); void transfer_from_interpreter(Interpreter* interpreter); @@ -720,11 +722,13 @@ class Stack : public HeapObject { void _set_length(int value) { _word_at_put(LENGTH_OFFSET, value); } void _set_top(int value) { _word_at_put(TOP_OFFSET, value); } void _set_try_top(int value) { _word_at_put(TRY_TOP_OFFSET, value); } + void _initialize(int length) { _set_length(length); _set_top(length); _set_try_top(length); } + Object** _stack_base_addr() { return reinterpret_cast(_raw_at(_array_offset_from(length()))); } Object** _stack_limit_addr() { return reinterpret_cast(_raw_at(_array_offset_from(0))); } Object** _stack_sp_addr() { return reinterpret_cast(_raw_at(_array_offset_from(top()))); } @@ -748,8 +752,8 @@ class Stack : public HeapObject { } uword* _array_address(int index) { return _raw_at(_array_offset_from(index)); } - static int _array_offset_from(int index) { return HEADER_SIZE + index * WORD_SIZE; } + friend class ObjectHeap; friend class ProgramHeap; }; @@ -1073,6 +1077,8 @@ class Method { uint8* bcp_from_bci(int bci) const { return &_bytes[ENTRY_OFFSET + bci]; } uint8* header_bcp() const { return _bytes; } + static uint8* header_from_entry(uint8* entry) { return entry - ENTRY_OFFSET; } + private: // Friend access for ProgramBuilder. void _initialize_block(int arity, List bytecodes, int max_height) { _initialize(BLOCK, 0, arity, bytecodes, max_height); diff --git a/src/os.h b/src/os.h index 107e86787..1a2e6aa4d 100644 --- a/src/os.h +++ b/src/os.h @@ -187,7 +187,7 @@ class OS { static ConditionVariable* allocate_condition_variable(Mutex* mutex); static void wait(ConditionVariable* condition_variable); // Returns false if a timeout occurs. - static bool wait(ConditionVariable* condition_variable, int timeout_in_ms); + static bool wait_us(ConditionVariable* condition_variable, int64 us); static void signal(ConditionVariable* condition_variable); static void signal_all(ConditionVariable* condition_variable); static void dispose(ConditionVariable* condition_variable); diff --git a/src/os_esp32.cc b/src/os_esp32.cc index 2f5054ad1..a7db062c4 100644 --- a/src/os_esp32.cc +++ b/src/os_esp32.cc @@ -141,20 +141,24 @@ class ConditionVariable { } void wait() { - wait(0); + wait_ticks(portMAX_DELAY); } - bool wait(int timeout_in_ms) { + bool wait_us(int64 us) { + if (us <= 0LL) return false; + + // Use ceiling divisions to avoid rounding the ticks down and thus + // not waiting long enough. + uint32 ms = 1 + static_cast((us - 1) / 1000LL); + uint32 ticks = (ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS; + return wait_ticks(ticks); + } + + bool wait_ticks(uint32 ticks) { if (!_mutex->is_locked()) { FATAL("wait on unlocked mutex"); } - // Use ceiling division to avoid rounding the ticks down and thus - // not waiting long enough. - int timeout_ticks = (timeout_in_ms > 0) - ? (timeout_in_ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS - : portMAX_DELAY; - ConditionVariableWaiter w = { .task = xTaskGetCurrentTaskHandle() }; @@ -168,7 +172,7 @@ class ConditionVariable { #else uint32 value = 0; #endif - bool success = xTaskNotifyWait(0x00, 0xffffffff, &value, timeout_ticks) == pdTRUE; + bool success = xTaskNotifyWait(0x00, 0xffffffff, &value, ticks) == pdTRUE; _mutex->lock(); TAILQ_REMOVE(&_waiter_list, &w, link); @@ -344,11 +348,11 @@ void OS::unlock(Mutex* mutex) { mutex->unlock(); } // Condition variable forwarders. ConditionVariable* OS::allocate_condition_variable(Mutex* mutex) { return _new ConditionVariable(mutex); } -void OS::wait(ConditionVariable* condition_variable) { condition_variable->wait(); } -bool OS::wait(ConditionVariable* condition_variable, int timeout_in_ms) { return condition_variable->wait(timeout_in_ms); } -void OS::signal(ConditionVariable* condition_variable) { condition_variable->signal(); } -void OS::signal_all(ConditionVariable* condition_variable) { condition_variable->signal_all(); } -void OS::dispose(ConditionVariable* condition_variable) { delete condition_variable; } +void OS::wait(ConditionVariable* condition) { condition->wait(); } +bool OS::wait_us(ConditionVariable* condition, int64 us) { return condition->wait_us(us); } +void OS::signal(ConditionVariable* condition) { condition->signal(); } +void OS::signal_all(ConditionVariable* condition) { condition->signal_all(); } +void OS::dispose(ConditionVariable* condition) { delete condition; } void* OS::allocate_pages(uword size) { size = Utils::round_up(size, TOIT_PAGE_SIZE); diff --git a/src/os_posix.cc b/src/os_posix.cc index 54eb36f66..9f7e68302 100644 --- a/src/os_posix.cc +++ b/src/os_posix.cc @@ -94,19 +94,15 @@ class ConditionVariable { } } - bool wait(int timeout_in_ms) { - if (timeout_in_ms == 0) { - // No timeout. - wait(); - return true; - } + bool wait_us(int64 us) { + if (us <= 0LL) return false; // TODO: We really should use monotonic time here. struct timespec deadline = { 0, }; if (!OS::get_real_time(&deadline)) { FATAL("cannot get time for deadline"); } - OS::timespec_increment(&deadline, timeout_in_ms * 1000000LL); + OS::timespec_increment(&deadline, us * 1000LL); int error = pthread_cond_timedwait(&_cond, &_mutex->_mutex, &deadline); if (error == 0) return true; if (error == ETIMEDOUT) return false; @@ -243,11 +239,11 @@ void OS::unlock(Mutex* mutex) { mutex->unlock(); } // Condition variable forwarders. ConditionVariable* OS::allocate_condition_variable(Mutex* mutex) { return _new ConditionVariable(mutex); } -void OS::wait(ConditionVariable* condition_variable) { condition_variable->wait(); } -bool OS::wait(ConditionVariable* condition_variable, int timeout_in_ms) { return condition_variable->wait(timeout_in_ms); } -void OS::signal(ConditionVariable* condition_variable) { condition_variable->signal(); } -void OS::signal_all(ConditionVariable* condition_variable) { condition_variable->signal_all(); } -void OS::dispose(ConditionVariable* condition_variable) { delete condition_variable; } +void OS::wait(ConditionVariable* condition) { condition->wait(); } +bool OS::wait_us(ConditionVariable* condition, int64 us) { return condition->wait_us(us); } +void OS::signal(ConditionVariable* condition) { condition->signal(); } +void OS::signal_all(ConditionVariable* condition) { condition->signal_all(); } +void OS::dispose(ConditionVariable* condition) { delete condition; } void OS::close(int fd) { ::close(fd); diff --git a/src/os_win.cc b/src/os_win.cc index 7e4fed489..88d82e531 100644 --- a/src/os_win.cc +++ b/src/os_win.cc @@ -95,19 +95,15 @@ class ConditionVariable { } } - bool wait(int timeout_in_ms) { - if (timeout_in_ms == 0) { - // No timeout. - wait(); - return true; - } + bool wait_us(int64 us) { + if (us <= 0LL) return false; // TODO: We really should use monotonic time here. struct timespec deadline = { 0, }; if (!OS::get_real_time(&deadline)) { FATAL("cannot get time for deadline"); } - OS::timespec_increment(&deadline, timeout_in_ms * 1000000LL); + OS::timespec_increment(&deadline, us * 1000LL); int error = pthread_cond_timedwait(&_cond, &_mutex->_mutex, &deadline); if (error == 0) return true; if (error == ETIMEDOUT) return false; @@ -244,11 +240,11 @@ void OS::unlock(Mutex* mutex) { mutex->unlock(); } // Condition variable forwarders. ConditionVariable* OS::allocate_condition_variable(Mutex* mutex) { return _new ConditionVariable(mutex); } -void OS::wait(ConditionVariable* condition_variable) { condition_variable->wait(); } -bool OS::wait(ConditionVariable* condition_variable, int timeout_in_ms) { return condition_variable->wait(timeout_in_ms); } -void OS::signal(ConditionVariable* condition_variable) { condition_variable->signal(); } -void OS::signal_all(ConditionVariable* condition_variable) { condition_variable->signal_all(); } -void OS::dispose(ConditionVariable* condition_variable) { delete condition_variable; } +void OS::wait(ConditionVariable* condition) { condition->wait(); } +bool OS::wait_us(ConditionVariable* condition, int64 us) { return condition->wait_us(us); } +void OS::signal(ConditionVariable* condition) { condition->signal(); } +void OS::signal_all(ConditionVariable* condition) { condition->signal_all(); } +void OS::dispose(ConditionVariable* condition) { delete condition; } void OS::close(int fd) {} diff --git a/src/primitive_core.cc b/src/primitive_core.cc index 8b1073c0a..2d9963df4 100644 --- a/src/primitive_core.cc +++ b/src/primitive_core.cc @@ -2030,50 +2030,34 @@ PRIMITIVE(rebuild_hash_index) { PRIMITIVE(profiler_install) { ARGS(bool, profile_all_tasks); -#ifdef PROFILER if (process->profiler() != null) ALREADY_EXISTS; int result = process->install_profiler(profile_all_tasks ? -1 : process->task()->id()); if (result == -1) MALLOC_FAILED; return Smi::from(result); -#else - USE(profile_all_tasks); - PERMISSION_DENIED; -#endif } PRIMITIVE(profiler_start) { -#ifdef PROFILER Profiler* profiler = process->profiler(); if (profiler == null) ALREADY_CLOSED; if (profiler->is_active()) return process->program()->false_object(); profiler->start(); - // Force the interpreter to recompute if profiling is active. - process->scheduler_thread()->interpreter()->store_stack(); - process->scheduler_thread()->interpreter()->load_stack(); + // Tell the scheduler that a new process has an active profiler. + VM::current()->scheduler()->activate_profiler(process); return process->program()->true_object(); -#else - PERMISSION_DENIED; -#endif } PRIMITIVE(profiler_stop) { -#ifdef PROFILER Profiler* profiler = process->profiler(); if (profiler == null) ALREADY_CLOSED; if (!profiler->is_active()) return process->program()->false_object(); profiler->stop(); - // Force the interpreter to recompute if profiling is active. - process->scheduler_thread()->interpreter()->store_stack(); - process->scheduler_thread()->interpreter()->load_stack(); + // Tell the scheduler to deactivate profiling for the process. + VM::current()->scheduler()->deactivate_profiler(process); return process->program()->true_object(); -#else - PERMISSION_DENIED; -#endif } PRIMITIVE(profiler_encode) { ARGS(String, title, int, cutoff); -#ifdef PROFILER Profiler* profiler = process->profiler(); if (profiler == null) ALREADY_CLOSED; MallocedBuffer buffer(4096); @@ -2086,22 +2070,13 @@ PRIMITIVE(profiler_encode) { ByteArray::Bytes bytes(result); memcpy(bytes.address(), buffer.content(), buffer.size()); return result; -#else - USE(title); - USE(cutoff); - PERMISSION_DENIED; -#endif } PRIMITIVE(profiler_uninstall) { -#ifdef PROFILER Profiler* profiler = process->profiler(); if (profiler == null) ALREADY_CLOSED; process->uninstall_profiler(); return process->program()->null_object(); -#else - PERMISSION_DENIED; -#endif } PRIMITIVE(set_max_heap_size) { diff --git a/src/printing.cc b/src/printing.cc index 7ef151bae..a42b53ee4 100644 --- a/src/printing.cc +++ b/src/printing.cc @@ -120,9 +120,8 @@ void print_bytecode(Printer* printer, uint8* bcp, int bci) { case OP_BF: printer->printf(" T%u", bci + index); break; - case OP_SB: + case OP_SB_SB: index = Utils::read_unaligned_uint16(bcp + 1); - case OP_BB: printer->printf(" T%d", bci - index); break; case OP_SCI: diff --git a/src/process.h b/src/process.h index 5b6edea9f..2847582f1 100644 --- a/src/process.h +++ b/src/process.h @@ -179,20 +179,20 @@ class Process : public ProcessListFromProcessGroup::Element, return result; } - #ifdef PROFILER - int install_profiler(int task_id) { - ASSERT(profiler() == null); - _profiler = _new Profiler(task_id); - if (_profiler == null) return -1; - return profiler()->allocated_bytes(); - } - Profiler* profiler() { return _profiler; } - void uninstall_profiler() { - Profiler* p = profiler(); - _profiler = null; - delete p; - } - #endif + Profiler* profiler() const { return _profiler; } + + int install_profiler(int task_id) { + ASSERT(profiler() == null); + _profiler = _new Profiler(task_id); + if (_profiler == null) return -1; + return profiler()->allocated_bytes(); + } + + void uninstall_profiler() { + Profiler* p = profiler(); + _profiler = null; + delete p; + } void set_last_run(int64 us) { _last_run_us = us; @@ -260,9 +260,7 @@ class Process : public ProcessListFromProcessGroup::Element, int64 _last_run_us = 0; int64 _unyielded_for_us = 0; -#ifdef PROFILER Profiler* _profiler = null; -#endif ResourceGroupListFromProcess _resource_groups; friend class HeapObject; diff --git a/src/profiler.cc b/src/profiler.cc index b8f040969..15daf14f8 100644 --- a/src/profiler.cc +++ b/src/profiler.cc @@ -18,8 +18,6 @@ #include "profiler.h" #include "encoder.h" -#ifdef PROFILER - namespace toit { Profiler::Profiler(int task_id) : task_id_(task_id) { @@ -54,7 +52,7 @@ void Profiler::stop() { void Profiler::print() { printf("Profile:\n"); - for (int index = 0; index < table_size; index++) { + for (int index = 1; index < table_size; index++) { int method_id = offset_table[index]; int64 count = counter_table[index]; if (count > 0) printf(" %5d:%8lld\n", method_id, static_cast(count)); @@ -64,13 +62,13 @@ void Profiler::print() { void Profiler::encode_on(ProgramOrientedEncoder* encoder, String* title, int cutoff) { // Compute total number of counts. int64 total_count = 0; - for (int index = 0; index < table_size; index++) { + for (int index = 1; index < table_size; index++) { total_count += counter_table[index]; } // Compute number of reported lines based on cutoff. const int64 cutoff_count = (int64) (((double) total_count * cutoff) / 1000.0); int real_entries = 0; - for (int index = 0; index < table_size; index++) { + for (int index = 1; index < table_size; index++) { if (counter_table[index] > cutoff_count) real_entries++; } // Encode the report. @@ -78,7 +76,7 @@ void Profiler::encode_on(ProgramOrientedEncoder* encoder, String* title, int cut encoder->encode(title); encoder->write_int(cutoff); encoder->write_int(total_count); - for (int index = 0; index < table_size; index++) { + for (int index = 1; index < table_size; index++) { if (counter_table[index] > cutoff_count) { int method_id = offset_table[index]; encoder->write_int(method_id); @@ -172,5 +170,3 @@ int Profiler::compute_index_for_absolute_bci(int absolute_bci) { } } // namespace toit - -#endif diff --git a/src/profiler.h b/src/profiler.h index e7ad1f29d..f46bd675e 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -17,8 +17,6 @@ #include "top.h" -#ifdef PROFILER - namespace toit { // This is a simple profile designed for an interpreter and minimal space usage. @@ -66,5 +64,3 @@ class Profiler { }; } // namespace toit - -#endif diff --git a/src/scheduler.cc b/src/scheduler.cc index 4190b359b..3948d002f 100644 --- a/src/scheduler.cc +++ b/src/scheduler.cc @@ -144,18 +144,16 @@ Scheduler::ExitState Scheduler::launch_program(Locker& locker, Process* process) _boot_process = process; add_process(locker, process); - int64 next_tick_time = OS::get_monotonic_time() + TICK_PERIOD_US; - + tick_schedule(locker, OS::get_monotonic_time(), true); while (_num_processes > 0 && _num_threads > 0) { int64 time = OS::get_monotonic_time(); - if (time >= next_tick_time) { - next_tick_time = time + Scheduler::TICK_PERIOD_US; - tick(locker); + int64 next = tick_next(); + if (time >= next) { + tick(locker, time); + } else { + int64 delay_us = next - time; + OS::wait_us(_has_threads, delay_us); } - ASSERT(time < next_tick_time); - int delay_ms = 1 + ((next_tick_time - time - 1) / 1000); // Ceiling division. - - OS::wait(_has_threads, delay_ms); } if (!has_exit_reason()) { @@ -274,7 +272,7 @@ scheduler_err_t Scheduler::send_system_message(Locker& locker, SystemMessage* me } break; case SystemMessage::SPAWNED: { - // Do nothing. With no boot process, we don't care newly about spawned processes. + // Do nothing. With no boot process, we don't care about newly spawned processes. break; } default: @@ -433,10 +431,9 @@ void Scheduler::gc(Process* process, bool malloc_failed, bool try_hard) { // to be preempted, but since we only GC them if we can get them to // be "suspendable" or "suspended" later, we can live with this // timing out and not succeeding. - int64 deadline = start + 1000000; // Wait for up to 1 second. + int64 deadline = start + 1000000LL; // Wait for up to 1 second. while (_gc_waiting_for_preemption > 0) { - int64 wait_ms = Utils::max(1LL, (deadline - OS::get_monotonic_time()) / 1000); - if (!OS::wait(_gc_condition, wait_ms)) { + if (!OS::wait_us(_gc_condition, deadline - OS::get_monotonic_time())) { #ifdef TOIT_GC_LOGGING printf("[gc @ %p%s | timed out waiting for %d processes to stop]\n", process, VM::current()->scheduler()->is_boot_process(process) ? "*" : " ", @@ -569,7 +566,12 @@ void Scheduler::run_process(Locker& locker, Process* process, SchedulerThread* s ProcessRunner* runner = process->runner(); bool interpreted = (runner == null); Interpreter::Result result(Interpreter::Result::PREEMPTED); + uint8* preemption_method_header_bcp = null; if (interpreted) { + if (process->profiler() && process->profiler()->is_active()) { + notify_profiler(locker, 1); + } + Interpreter* interpreter = scheduler_thread->interpreter(); interpreter->activate(process); process->set_idle_since_gc(false); @@ -577,7 +579,12 @@ void Scheduler::run_process(Locker& locker, Process* process, SchedulerThread* s Unlocker unlock(locker); result = interpreter->run(); } + preemption_method_header_bcp = interpreter->preemption_method_header_bcp(); interpreter->deactivate(); + + if (process->profiler() && process->profiler()->is_active()) { + notify_profiler(locker, -1); + } } else if (process->signals() == 0) { ASSERT(process->idle_since_gc()); Unlocker unlock(locker); @@ -605,10 +612,25 @@ void Scheduler::run_process(Locker& locker, Process* process, SchedulerThread* s } switch (result.state()) { - case Interpreter::Result::PREEMPTED: + case Interpreter::Result::PREEMPTED: { + Profiler* profiler = process->profiler(); + Task* task = process->task(); + if (profiler && task && profiler->should_profile_task(task->id())) { + Stack* stack = task->stack(); + if (stack) { + int bci = stack->absolute_bci_at_preemption(process->program()); + ASSERT(preemption_method_header_bcp); + if (bci >= 0 && preemption_method_header_bcp) { + int method = process->program()->absolute_bci_from_bcp(preemption_method_header_bcp); + profiler->register_method(method); + profiler->increment(bci); + } + } + } wait_for_any_gc_to_complete(locker, process, Process::IDLE); process_ready(locker, process); break; + } case Interpreter::Result::YIELDED: process->clear_unyielded_for(); @@ -754,8 +776,8 @@ void Scheduler::terminate_execution(Locker& locker, ExitState exit) { OS::signal(_has_processes); } -void Scheduler::tick(Locker& locker) { - int64 now = OS::get_monotonic_time(); +void Scheduler::tick(Locker& locker, int64 now) { + tick_schedule(locker, now, true); for (SchedulerThread* thread : _threads) { Process* process = thread->interpreter()->process(); @@ -767,7 +789,11 @@ void Scheduler::tick(Locker& locker) { } } - if (_ready_processes.is_empty()) return; + if (_num_profiled_processes == 0 && _ready_processes.is_empty()) { + // No need to do preemption when there are no active profilers + // and no other processes ready to run. + return; + } for (SchedulerThread* thread : _threads) { Process* process = thread->interpreter()->process(); @@ -777,6 +803,26 @@ void Scheduler::tick(Locker& locker) { } } +void Scheduler::tick_schedule(Locker& locker, int64 now, bool reschedule) { + int period = (_num_profiled_processes > 0) + ? TICK_PERIOD_PROFILING_US + : TICK_PERIOD_US; + int64 next = now + period; + if (!reschedule && next >= tick_next()) return; + _next_tick = next; + if (!reschedule) OS::signal(_has_threads); +} + +void Scheduler::notify_profiler(int change) { + Locker locker(_mutex); + notify_profiler(locker, change); +} + +void Scheduler::notify_profiler(Locker& locker, int change) { + _num_profiled_processes += change; + tick_schedule(locker, OS::get_monotonic_time(), false); +} + Process* Scheduler::find_process(Locker& locker, int process_id) { for (ProcessGroup* group : _groups) { Process* p = group->lookup(process_id); diff --git a/src/scheduler.h b/src/scheduler.h index 11aa49f65..c29eb4d1a 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -129,6 +129,10 @@ class Scheduler { // processes in the system. void gc(Process* process, bool malloc_failed, bool try_hard); + // Profiler support. + void activate_profiler(Process* process) { notify_profiler(1); } + void deactivate_profiler(Process* process) { notify_profiler(-1); } + // Primitive support. // Fills in an array with stats for the process with the given ids. @@ -144,11 +148,13 @@ class Scheduler { // Introduce a new process to the Scheduler. The Scheduler will not terminate until // all processes has completed. void new_process(Locker& locker, Process* process); - void add_process(Locker& locker, Process* process); - void run_process(Locker& locker, Process* process, SchedulerThread* scheduler_thread); + // Profiler support. + void notify_profiler(int change); + void notify_profiler(Locker& locker, int change); + // Suspend/resume support for processes. Allows other threads to temporarily suspend // a process and remove it from the ready list (if it's not idle). Resuming a process // puts the threads back into its original state, modulo idle->scheduled transitions that @@ -182,15 +188,26 @@ class Scheduler { SystemMessage* new_process_message(SystemMessage::Type type, int gid); - // Called by the launch thread, to signal that time has passed. - // The tick is used to drive process preemption. - static const int64 TICK_PERIOD_US = 100000; // 100 ms. #ifdef TOIT_FREERTOS - static const int64 WATCHDOG_PERIOD_US = 10 * 1000 * 1000; // 10 s. + static const int64 WATCHDOG_PERIOD_US = 10 * 1000 * 1000; // 10 s. #else static const int64 WATCHDOG_PERIOD_US = 600 * 1000 * 1000; // 10 m. #endif - void tick(Locker& locker); + + static const int TICK_PERIOD_US = 100 * 1000; // 100 ms. +#ifdef TOIT_FREERTOS + static const int TICK_PERIOD_PROFILING_US = 10 * 100; // 10 ms. +#else + static const int TICK_PERIOD_PROFILING_US = 500; // 0.5 ms. +#endif + + // Called by the launch thread to signal that time has passed. + // The tick is used to drive process preemption. + void tick(Locker& locker, int64 now); + void tick_schedule(Locker& locker, int64 now, bool reschedule); + + // Get the time for the next tick for process preemption. + int64 tick_next() const { return _next_tick; } Mutex* _mutex; ConditionVariable* _has_processes; @@ -209,12 +226,16 @@ class Scheduler { int _num_processes; int _next_group_id; int _next_process_id; + int64 _next_tick = 0; ProcessListFromScheduler _ready_processes; int _num_threads; int _max_threads; SchedulerThreadList _threads; + // Keep track of the number of ready processes with an active profiler. + int _num_profiled_processes = 0; + // Keep track of the boot process if it still alive. Process* _boot_process; diff --git a/src/top.h b/src/top.h index ab631b7d9..a9b219a15 100644 --- a/src/top.h +++ b/src/top.h @@ -111,9 +111,6 @@ #define CONFIG_TOIT_BIT_DISPLAY 1 #endif -// Define PROFILER if the bytecode profiler should be included. -#define PROFILER - typedef intptr_t word; typedef uintptr_t uword; @@ -326,21 +323,18 @@ namespace compiler { class ProgramBuilder; } -class Object; -class Smi; class Array; class ByteArray; -class Instance; -class HeapObject; class Double; -class Stack; -class Task; -class String; +class HeapObject; +class Instance; class LargeInteger; - -#ifdef PROFILER +class Object; class Profiler; -#endif +class Smi; +class Stack; +class String; +class Task; // If you capture too many variables, then the functor does heap allocations. // These can fail on the device, and we can't catch that deep in the compiler's diff --git a/src/utils.h b/src/utils.h index 0e05712ec..ec1641916 100644 --- a/src/utils.h +++ b/src/utils.h @@ -358,6 +358,10 @@ class List { T* end() { return &_data[_length]; } const T* end() const { return &_data[_length]; } + bool is_inside(const T* pointer) const { + return (pointer >= begin() && pointer < end()); + } + const List sublist(int from, int to) const { ASSERT(0 <= from && from <= to && to < _length); return List(&_data[from], to - from); diff --git a/tests/negative/gold/invalid_program_test.gold b/tests/negative/gold/invalid_program_test.gold index 0a8b82c66..0ed1eba51 100644 --- a/tests/negative/gold/invalid_program_test.gold +++ b/tests/negative/gold/invalid_program_test.gold @@ -1,4 +1,4 @@ INVALID_PROGRAM error. -1915 +1921 0: run_global_initializer_ /core/objects.toit:259:1 1: main tests/negative/invalid_program_test.toit:9:3 diff --git a/tests/profiler/basic_input.toit b/tests/profiler/basic_input.toit index 8a78291c0..7ea521956 100644 --- a/tests/profiler/basic_input.toit +++ b/tests/profiler/basic_input.toit @@ -13,14 +13,14 @@ bar: foo: sum := 0 - for i := 0; i < ITERATIONS; i++: + for i := 0; i < ITERATIONS * 2; i++: sum += i - expect_equals 499500 sum + expect_equals 1999000 sum bar main: Profiler.install false - Profiler.do: foo + Profiler.do: 10_000.repeat: foo Profiler.report "Profiler Test" Profiler.uninstall diff --git a/tests/profiler/basic_test.toit b/tests/profiler/basic_test.toit index 2e638e4f4..1e24cbbb3 100644 --- a/tests/profiler/basic_test.toit +++ b/tests/profiler/basic_test.toit @@ -7,6 +7,7 @@ import expect show * main args: lines := run args + print (lines.join "\n") expect (lines.first.starts_with "Profile of Profiler Test") expect_equals "foo" (lines[1].copy 7 30).trim diff --git a/tests/profiler/lambda_input.toit b/tests/profiler/lambda_input.toit index 6c5131740..776d1d057 100644 --- a/tests/profiler/lambda_input.toit +++ b/tests/profiler/lambda_input.toit @@ -18,14 +18,14 @@ bar: foo: run:: sum := 0 - for i := 0; i < ITERATIONS; i++: + for i := 0; i < ITERATIONS * 2; i++: sum += i - expect_equals 499500 sum + expect_equals 1999000 sum bar main: Profiler.install false - Profiler.do: foo + Profiler.do: 10_000.repeat: foo Profiler.report "Lambda Profiler Test" Profiler.uninstall diff --git a/tests/profiler/lambda_test.toit b/tests/profiler/lambda_test.toit index 22c2cbd2b..ee1885f20 100644 --- a/tests/profiler/lambda_test.toit +++ b/tests/profiler/lambda_test.toit @@ -7,7 +7,7 @@ import expect show * main args: lines := run args - print lines + print (lines.join "\n") expect (lines.first.starts_with "Profile of Lambda Profiler Test") expect_equals "[lambda] in foo" (lines[1].copy 7 35).trim diff --git a/tests/profiler/utils.toit b/tests/profiler/utils.toit index 55c82ce05..5af242f48 100644 --- a/tests/profiler/utils.toit +++ b/tests/profiler/utils.toit @@ -6,14 +6,14 @@ import host.pipe import reader show BufferedReader /** -Runs the given test with $args containing `toitc` as first argument, and +Runs the given test with $args containing `toit.run` as first argument, and the input as second. Returns the lines of the output. Throws if the program didn't terminate with exit code 0. */ run args -> List: - toitc := args[0] + toitrun := args[0] profiled_path := args[1] pipes := pipe.fork @@ -21,8 +21,8 @@ run args -> List: pipe.PIPE_INHERITED // stdin pipe.PIPE_INHERITED // stdout pipe.PIPE_CREATED // stderr - toitc - [ toitc, profiled_path ] + toitrun + [ toitrun, profiled_path ] stderr := pipes[2] pid := pipes[3] diff --git a/tools/mirror.toit b/tools/mirror.toit index c088779cb..8af45abaa 100644 --- a/tools/mirror.toit +++ b/tools/mirror.toit @@ -323,7 +323,7 @@ class Profile extends Mirror: return result.join "\n" stringify -> string: - return "Profile of $title ($total bytecodes executed, cutoff $(cutoff.to_float/10)%):\n$table" + return "Profile of $title ($total ticks, cutoff $(cutoff.to_float/10)%):\n$table" class HistogramEntry: class_name /string diff --git a/tools/snapshot.toit b/tools/snapshot.toit index c7a673ad3..cc8e1784c 100644 --- a/tools/snapshot.toit +++ b/tools/snapshot.toit @@ -547,9 +547,7 @@ class ToitMethod: line += " T$(bci + index)" else if format == OP_SF: line += " T$(bci + (method.uint16 bci + 1))" - else if format == OP_BB: - line += " T$(bci - index)" - else if format == OP_SB: + else if format == OP_SB_SB: line += " T$(bci - (method.uint16 bci + 1))" else if format == OP_BCI: is_nullable := (index & 1) != 0 @@ -729,30 +727,29 @@ OP_BL ::= 4 OP_BC ::= 5 OP_BG ::= 6 OP_BF ::= 7 -OP_BB ::= 8 -OP_BCI ::= 9 -OP_BII ::= 10 -OP_BLC ::= 11 -OP_SU ::= 12 -OP_SF ::= 13 -OP_BS_BU ::= 14 -OP_SD ::= 15 -OP_SO ::= 16 -OP_WU ::= 17 -OP_BS_SO ::= 18 -OP_BU_SO ::= 19 -OP_BU_SU ::= 20 -OP_BU_WU ::= 21 -OP_SD_BS_BU ::= 22 -OP_SS ::= 23 -OP_SL ::= 24 -OP_SG ::= 25 -OP_SC ::= 26 -OP_SS_SO ::= 27 -OP_SCI ::= 28 -OP_SII ::= 29 -OP_SB ::= 30 -OP_SU_SU ::= 31 +OP_BCI ::= 8 +OP_BII ::= 9 +OP_BLC ::= 10 +OP_SU ::= 11 +OP_SF ::= 12 +OP_BS_BU ::= 13 +OP_SD ::= 14 +OP_SO ::= 15 +OP_WU ::= 16 +OP_BS_SO ::= 17 +OP_BU_SO ::= 18 +OP_BU_SU ::= 19 +OP_BU_WU ::= 20 +OP_SD_BS_BU ::= 21 +OP_SS ::= 22 +OP_SL ::= 23 +OP_SG ::= 24 +OP_SC ::= 25 +OP_SS_SO ::= 26 +OP_SCI ::= 27 +OP_SII ::= 28 +OP_SB_SB ::= 29 +OP_SU_SU ::= 30 class Bytecode: name ::= "" @@ -846,12 +843,9 @@ BYTE_CODES ::= [ Bytecode "BRANCH" 3 OP_SF "branch", Bytecode "BRANCH_IF_TRUE" 3 OP_SF "branch if true", Bytecode "BRANCH_IF_FALSE" 3 OP_SF "branch if false", - Bytecode "BRANCH_BACK" 2 OP_BB "branch back", - Bytecode "BRANCH_BACK_WIDE" 3 OP_SB "branch back wide", - Bytecode "BRANCH_BACK_IF_TRUE" 2 OP_BB "branch back if true", - Bytecode "BRANCH_BACK_IF_TRUE_WIDE" 3 OP_SB "branch back if true wide", - Bytecode "BRANCH_BACK_IF_FALSE" 2 OP_BB "branch back if false", - Bytecode "BRANCH_BACK_IF_FALSE_WIDE" 3 OP_SB "branch back if false wide", + Bytecode "BRANCH_BACK" 5 OP_SB_SB "branch back", + Bytecode "BRANCH_BACK_IF_TRUE" 5 OP_SB_SB "branch back if true", + Bytecode "BRANCH_BACK_IF_FALSE" 5 OP_SB_SB "branch back if false", Bytecode "PRIMITIVE" 4 OP_BU_SU "invoke primitive", Bytecode "THROW" 2 OP_BU "throw", Bytecode "RETURN" 3 OP_BS_BU "return",