Skip to content

Commit

Permalink
LibJS: FDI bytecode WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kalenikaliaksandr committed May 9, 2024
1 parent 26c74a9 commit df0c99b
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 178 deletions.
190 changes: 12 additions & 178 deletions Userland/Libraries/LibJS/Bytecode/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,11 @@ CodeGenerationErrorOr<void> Generator::emit_fdi(ECMAScriptFunctionObject const&
return {};
}

CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ECMAScriptFunctionObject const& function)
template<typename Callback>
CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::emit_function_body_bytecode(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, Callback emit_fdi_if_needed)
{
Generator generator(vm);

auto const& node = function.ecmascript_code();
auto enclosing_function_kind = function.kind();

generator.switch_to_basic_block(generator.make_block());
SourceLocationScope scope(generator, node);
generator.m_enclosing_function_kind = enclosing_function_kind;
Expand All @@ -215,7 +213,7 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ECMA
// will not enter the generator from the SuspendedStart state and immediately completes the generator.
}

TRY(generator.emit_fdi(function));
TRY(emit_fdi_if_needed(generator));

if (generator.is_in_generator_function()) {
// Immediately yield with no value.
Expand Down Expand Up @@ -389,181 +387,17 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ECMA

CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind)
{
Generator generator(vm);

generator.switch_to_basic_block(generator.make_block());
SourceLocationScope scope(generator, node);
generator.m_enclosing_function_kind = enclosing_function_kind;
if (generator.is_in_generator_or_async_function()) {
// Immediately yield with no value.
auto& start_block = generator.make_block();
generator.emit<Bytecode::Op::Yield>(Label { start_block }, generator.add_constant(js_undefined()));
generator.switch_to_basic_block(start_block);
// NOTE: This doesn't have to handle received throw/return completions, as GeneratorObject::resume_abrupt
// will not enter the generator from the SuspendedStart state and immediately completes the generator.
}

auto last_value = TRY(node.generate_bytecode(generator));

if (!generator.current_block().is_terminated() && last_value.has_value()) {
generator.emit<Bytecode::Op::End>(last_value.value());
}

if (generator.is_in_generator_or_async_function()) {
// Terminate all unterminated blocks with yield return
for (auto& block : generator.m_root_basic_blocks) {
if (block->is_terminated())
continue;
generator.switch_to_basic_block(*block);
generator.emit<Bytecode::Op::Yield>(nullptr, generator.add_constant(js_undefined()));
}
}

bool is_strict_mode = false;
if (is<Program>(node))
is_strict_mode = static_cast<Program const&>(node).is_strict_mode();
else if (is<FunctionBody>(node))
is_strict_mode = static_cast<FunctionBody const&>(node).in_strict_mode();
else if (is<FunctionDeclaration>(node))
is_strict_mode = static_cast<FunctionDeclaration const&>(node).is_strict_mode();
else if (is<FunctionExpression>(node))
is_strict_mode = static_cast<FunctionExpression const&>(node).is_strict_mode();

size_t size_needed = 0;
for (auto& block : generator.m_root_basic_blocks) {
size_needed += block->size();
}

Vector<u8> bytecode;
bytecode.ensure_capacity(size_needed);

Vector<size_t> basic_block_start_offsets;
basic_block_start_offsets.ensure_capacity(generator.m_root_basic_blocks.size());

HashMap<BasicBlock const*, size_t> block_offsets;
Vector<size_t> label_offsets;

struct UnlinkedExceptionHandlers {
size_t start_offset;
size_t end_offset;
BasicBlock const* handler;
BasicBlock const* finalizer;
};
Vector<UnlinkedExceptionHandlers> unlinked_exception_handlers;

HashMap<size_t, SourceRecord> source_map;

for (auto& block : generator.m_root_basic_blocks) {
basic_block_start_offsets.append(bytecode.size());
if (block->handler() || block->finalizer()) {
unlinked_exception_handlers.append({
.start_offset = bytecode.size(),
.end_offset = 0,
.handler = block->handler(),
.finalizer = block->finalizer(),
});
}

block_offsets.set(block.ptr(), bytecode.size());

for (auto& [offset, source_record] : block->source_map()) {
source_map.set(bytecode.size() + offset, source_record);
}

Bytecode::InstructionStreamIterator it(block->instruction_stream());
while (!it.at_end()) {
auto& instruction = const_cast<Instruction&>(*it);

// OPTIMIZATION: Don't emit jumps that just jump to the next block.
if (instruction.type() == Instruction::Type::Jump) {
auto& jump = static_cast<Bytecode::Op::Jump&>(instruction);
if (jump.target().basic_block_index() == block->index() + 1) {
if (basic_block_start_offsets.last() == bytecode.size()) {
// This block is empty, just skip it.
basic_block_start_offsets.take_last();
}
++it;
continue;
}
}

// OPTIMIZATION: For `JumpIf` where one of the targets is the very next block,
// we can emit a `JumpTrue` or `JumpFalse` (to the other block) instead.
if (instruction.type() == Instruction::Type::JumpIf) {
auto& jump = static_cast<Bytecode::Op::JumpIf&>(instruction);
if (jump.true_target().basic_block_index() == block->index() + 1) {
Op::JumpFalse jump_false(jump.condition(), Label { jump.false_target() });
auto& label = jump_false.target();
size_t label_offset = bytecode.size() + (bit_cast<FlatPtr>(&label) - bit_cast<FlatPtr>(&jump_false));
label_offsets.append(label_offset);
bytecode.append(reinterpret_cast<u8 const*>(&jump_false), jump_false.length());
++it;
continue;
}
if (jump.false_target().basic_block_index() == block->index() + 1) {
Op::JumpTrue jump_true(jump.condition(), Label { jump.true_target() });
auto& label = jump_true.target();
size_t label_offset = bytecode.size() + (bit_cast<FlatPtr>(&label) - bit_cast<FlatPtr>(&jump_true));
label_offsets.append(label_offset);
bytecode.append(reinterpret_cast<u8 const*>(&jump_true), jump_true.length());
++it;
continue;
}
}

instruction.visit_labels([&](Label& label) {
size_t label_offset = bytecode.size() + (bit_cast<FlatPtr>(&label) - bit_cast<FlatPtr>(&instruction));
label_offsets.append(label_offset);
});
bytecode.append(reinterpret_cast<u8 const*>(&instruction), instruction.length());
++it;
}
if (!block->is_terminated()) {
Op::End end(generator.add_constant(js_undefined()));
bytecode.append(reinterpret_cast<u8 const*>(&end), end.length());
}
if (block->handler() || block->finalizer()) {
unlinked_exception_handlers.last().end_offset = bytecode.size();
}
}
for (auto label_offset : label_offsets) {
auto& label = *reinterpret_cast<Label*>(bytecode.data() + label_offset);
auto* block = generator.m_root_basic_blocks[label.basic_block_index()].ptr();
label.set_address(block_offsets.get(block).value());
}

auto executable = vm.heap().allocate_without_realm<Executable>(
move(bytecode),
move(generator.m_identifier_table),
move(generator.m_string_table),
move(generator.m_regex_table),
move(generator.m_constants),
node.source_code(),
generator.m_next_property_lookup_cache,
generator.m_next_global_variable_cache,
generator.m_next_environment_variable_cache,
generator.m_next_register,
is_strict_mode);

Vector<Executable::ExceptionHandlers> linked_exception_handlers;

for (auto& unlinked_handler : unlinked_exception_handlers) {
auto start_offset = unlinked_handler.start_offset;
auto end_offset = unlinked_handler.end_offset;
auto handler_offset = unlinked_handler.handler ? block_offsets.get(unlinked_handler.handler).value() : Optional<size_t> {};
auto finalizer_offset = unlinked_handler.finalizer ? block_offsets.get(unlinked_handler.finalizer).value() : Optional<size_t> {};
linked_exception_handlers.append({ start_offset, end_offset, handler_offset, finalizer_offset });
}

quick_sort(linked_exception_handlers, [](auto const& a, auto const& b) {
return a.start_offset < b.start_offset;
return emit_function_body_bytecode(vm, node, enclosing_function_kind, [&](Generator&) -> CodeGenerationErrorOr<void> {
// Do nothing
return {};
});
}

executable->exception_handlers = move(linked_exception_handlers);
executable->basic_block_start_offsets = move(basic_block_start_offsets);
executable->source_map = move(source_map);

return executable;
CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ECMAScriptFunctionObject const& function)
{
return emit_function_body_bytecode(vm, function.ecmascript_code(), function.kind(), [&](Bytecode::Generator& generator) {
return generator.emit_fdi(function);
});
}

void Generator::grow(size_t additional_size)
Expand Down
3 changes: 3 additions & 0 deletions Userland/Libraries/LibJS/Bytecode/Generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class Generator {
};
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> generate(VM&, ASTNode const&, FunctionKind = FunctionKind::Normal);

template<typename Callback>
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> emit_function_body_bytecode(VM&, ASTNode const&, FunctionKind, Callback);

CodeGenerationErrorOr<void> emit_fdi(ECMAScriptFunctionObject const& function);

Register allocate_register();
Expand Down

0 comments on commit df0c99b

Please sign in to comment.