diff --git a/src/ir/intrinsics.h b/src/ir/intrinsics.h index 2f6809c826b..d5654862e73 100644 --- a/src/ir/intrinsics.h +++ b/src/ir/intrinsics.h @@ -141,8 +141,8 @@ class Intrinsics { } // Given a call in a function, return all the annotations for it. The call may - // be annotated itself (which takes precedence), or the function it calls be - // annotated. + // be annotated itself (which takes precedence), or the function it calls may + // be annotated. CodeAnnotation getCallAnnotations(Call* call, Function* func) { // Combine annotations from the call itself and from the called function. auto ret = getAnnotations(call, func); @@ -168,6 +168,9 @@ class Intrinsics { if (!ret.idempotent) { ret.idempotent = funcAnnotations.idempotent; } + if (!ret.toolchainInline) { + ret.toolchainInline = funcAnnotations.toolchainInline; + } } return ret; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 06a515e32d1..39c37d07530 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1361,6 +1361,7 @@ struct AnnotationParserCtx { // overrides the others. const Annotation* branchHint = nullptr; const Annotation* inlineHint = nullptr; + const Annotation* toolchainInlineHint = nullptr; for (auto& a : annotations) { if (a.kind == Annotations::BranchHint) { branchHint = &a; @@ -1372,6 +1373,8 @@ struct AnnotationParserCtx { ret.jsCalled = true; } else if (a.kind == Annotations::IdempotentHint) { ret.idempotent = true; + } else if (a.kind == Annotations::ToolchainInlineHint) { + toolchainInlineHint = &a; } } @@ -1395,25 +1398,36 @@ struct AnnotationParserCtx { } } - // Apply the last inline hint, if valid. - if (inlineHint) { - Lexer lexer(inlineHint->contents); + auto parseI7Hint = [&](const Annotation* hint, + const char* name) -> std::optional { + if (!hint) { + return {}; + } + + Lexer lexer(hint->contents); if (lexer.empty()) { - std::cerr << "warning: empty InlineHint\n"; + std::cerr << "warning: empty " << name << "\n"; } else { auto str = lexer.takeString(); if (!str || str->size() != 1) { - std::cerr << "warning: invalid InlineHint string\n"; + std::cerr << "warning: invalid " << name << " string\n"; } else { uint8_t value = (*str)[0]; if (value > 127) { - std::cerr << "warning: invalid InlineHint value\n"; + std::cerr << "warning: invalid " << name << " value\n"; } else { - ret.inline_ = value; + return value; } } } - } + + return {}; + }; + + // Apply the last inline hints, if valid. + ret.inline_ = parseI7Hint(inlineHint, "InlineHint"); + ret.toolchainInline = + parseI7Hint(toolchainInlineHint, "Toolchain InlineHint"); return ret; } diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index cd328a7e1be..eeee2d8b8b8 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -114,6 +114,7 @@ struct FunctionInfo { bool usedGlobally; TrivialInstruction trivialInstruction; InliningMode inliningMode; + std::optional toolchainInlineHint; FunctionInfo() { clear(); } @@ -126,6 +127,7 @@ struct FunctionInfo { usedGlobally = false; trivialInstruction = TrivialInstruction::NotTrivial; inliningMode = InliningMode::Unknown; + toolchainInlineHint = std::nullopt; } // Provide an explicit = operator as the |refs| field lacks one by default. @@ -138,6 +140,7 @@ struct FunctionInfo { usedGlobally = other.usedGlobally; trivialInstruction = other.trivialInstruction; inliningMode = other.inliningMode; + toolchainInlineHint = other.toolchainInlineHint; return *this; } @@ -148,6 +151,13 @@ struct FunctionInfo { if (hasTryDelegate) { return false; } + // Follow the toolchain hint, if present. + if (toolchainInlineHint == CodeAnnotation::NeverInline) { + return false; + } + if (toolchainInlineHint == CodeAnnotation::AlwaysInline) { + return true; + } // If it's small enough that we always want to inline such things, do so. if (size <= options.inlining.alwaysInlineMaxSize) { return true; @@ -236,6 +246,7 @@ struct FunctionInfoScanner auto& info = infos[curr->name]; info.size = Measurer::measure(curr->body); + info.toolchainInlineHint = curr->funcAnnotations.toolchainInline; // If the body is a simple instruction with roughly the same encoded size as // a `call` instruction, and arguments are function locals read in order, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 86ce8595683..83e7baa580d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2838,6 +2838,17 @@ void PrintSExpression::printCodeAnnotations(const CodeAnnotation& annotation) { restoreNormalColor(o); doIndent(o, indent); } + if (annotation.toolchainInline) { + Colors::grey(o); + std::ofstream saved; + saved.copyfmt(o); + o << "(@" << Annotations::ToolchainInlineHint << " \"\\" << std::hex + << std::setfill('0') << std::setw(2) << int(*annotation.toolchainInline) + << "\")\n"; + o.copyfmt(saved); + restoreNormalColor(o); + doIndent(o, indent); + } } void PrintSExpression::printExpressionContents(Expression* curr) { diff --git a/src/passes/StripToolchainAnnotations.cpp b/src/passes/StripToolchainAnnotations.cpp index c2f61a74460..d1e17995add 100644 --- a/src/passes/StripToolchainAnnotations.cpp +++ b/src/passes/StripToolchainAnnotations.cpp @@ -59,6 +59,7 @@ struct StripToolchainAnnotations annotation.removableIfUnused = false; annotation.jsCalled = false; annotation.idempotent = false; + annotation.toolchainInline = std::nullopt; } }; diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h index b71424685c7..11c766ecf3a 100644 --- a/src/wasm-annotations.h +++ b/src/wasm-annotations.h @@ -30,6 +30,7 @@ extern const Name InlineHint; extern const Name RemovableIfUnusedHint; extern const Name JSCalledHint; extern const Name IdempotentHint; +extern const Name ToolchainInlineHint; } // namespace wasm::Annotations diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 92bb588d53c..225bb490752 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1480,6 +1480,7 @@ class WasmBinaryWriter { std::optional getRemovableIfUnusedHintsBuffer(); std::optional getJSCalledHintsBuffer(); std::optional getIdempotentHintsBuffer(); + std::optional getToolchainInlineHintsBuffer(); // helpers void writeInlineString(std::string_view name); @@ -1780,6 +1781,7 @@ class WasmBinaryReader { void readRemovableIfUnusedHints(size_t payloadLen); void readJSCalledHints(size_t payloadLen); void readIdempotentHints(size_t payloadLen); + void readToolchainInlineHints(size_t payloadLen); std::tuple readMemoryAccess(bool isAtomic, bool isRMW); diff --git a/src/wasm.h b/src/wasm.h index 9f81336144d..0eff9071b70 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2347,8 +2347,7 @@ struct CodeAnnotation { std::optional branchLikely; // Compilation Hints proposal. - static const uint8_t NeverInline = 0; - static const uint8_t AlwaysInline = 127; + enum { NeverInline = 0, AlwaysInline = 127 }; std::optional inline_; // Toolchain hints, see @@ -2378,6 +2377,11 @@ struct CodeAnnotation { // optimize things like Java class constructors. bool idempotent = false; + // An inlining hint at the toolchain level, in contrast to inline_, above, + // which is for VMs. (E.g., one may want to not inline at the toolchain level + // to keep size small, and tell VMs to inline at runtime.) + std::optional toolchainInline; + bool operator==(const CodeAnnotation& other) const { return equalOnSemanticsPreserving(other) && equalOnSemanticsAltering(other); } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 49497af127a..c25553ed5f3 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1662,6 +1662,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { append(getRemovableIfUnusedHintsBuffer()); append(getJSCalledHintsBuffer()); append(getIdempotentHintsBuffer()); + append(getToolchainInlineHintsBuffer()); return ret; } @@ -1783,23 +1784,24 @@ std::optional WasmBinaryWriter::getBranchHintsBuffer() { }); } -std::optional WasmBinaryWriter::getInlineHintsBuffer() { - return writeExpressionHints( - Annotations::InlineHint, - [](const CodeAnnotation& annotation) { return annotation.inline_; }, - [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { - // Hint size, always 1 for now. - buffer << U32LEB(1); - - // We must only emit hints that are present. - assert(annotation.inline_); - - // Hint must fit in one byte. - assert(*annotation.inline_ <= 127); - - // Hint contents: inline frequency count - buffer << U32LEB(*annotation.inline_); +// Writes a simple i7 hint, in the range [0..127]. +#define WRITE_I7_HINT(code, field) \ + return writeExpressionHints( \ + code, \ + [](const CodeAnnotation& annotation) { return annotation.field; }, \ + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { \ + /* Hint size, always 1 for now. */ \ + buffer << U32LEB(1); \ + /* We must only emit hints that are present. */ \ + assert(annotation.field); \ + /* Hint must fit in one byte. */ \ + assert(*annotation.field <= 127); \ + /* Hint contents: inline frequency count. */ \ + buffer << U32LEB(*annotation.field); \ }); + +std::optional WasmBinaryWriter::getInlineHintsBuffer() { + WRITE_I7_HINT(Annotations::InlineHint, inline_); } // Writes a simple boolean hint of size 0. Receives the code and the field name @@ -1827,6 +1829,11 @@ WasmBinaryWriter::getIdempotentHintsBuffer() { WRITE_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent); } +std::optional +WasmBinaryWriter::getToolchainInlineHintsBuffer() { + WRITE_I7_HINT(Annotations::ToolchainInlineHint, toolchainInline); +} + void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); @@ -2103,7 +2110,8 @@ void WasmBinaryReader::preScan() { sectionName == Annotations::InlineHint || sectionName == Annotations::RemovableIfUnusedHint || sectionName == Annotations::JSCalledHint || - sectionName == Annotations::IdempotentHint) { + sectionName == Annotations::IdempotentHint || + sectionName == Annotations::ToolchainInlineHint) { // Code annotations require code locations. // TODO: We could note which functions require code locations, as an // optimization. @@ -2271,6 +2279,11 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName == Annotations::IdempotentHint) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readIdempotentHints(payloadLen); }}); + } else if (sectionName == Annotations::ToolchainInlineHint) { + deferredAnnotationSections.push_back( + AnnotationSectionInfo{pos, [this, payloadLen]() { + this->readToolchainInlineHints(payloadLen); + }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5617,21 +5630,22 @@ void WasmBinaryReader::readBranchHints(size_t payloadLen) { }); } -void WasmBinaryReader::readInlineHints(size_t payloadLen) { - readExpressionHints( - Annotations::InlineHint, payloadLen, [&](CodeAnnotation& annotation) { - auto size = getU32LEB(); - if (size != 1) { - throwError("bad InlineHint size"); - } - - uint8_t inline_ = getInt8(); - if (inline_ > 127) { - throwError("bad InlineHint value"); - } +// Reads a simple i7 hint, in the range [0..127]. +#define READ_I7_HINT(code, field) \ + readExpressionHints(code, payloadLen, [&](CodeAnnotation& annotation) { \ + auto size = getU32LEB(); \ + if (size != 1) { \ + throwError("bad InlineHint size"); \ + } \ + uint8_t field = getInt8(); \ + if (field > 127) { \ + throwError("bad InlineHint value"); \ + } \ + annotation.field = field; \ + }); - annotation.inline_ = inline_; - }); +void WasmBinaryReader::readInlineHints(size_t payloadLen) { + READ_I7_HINT(Annotations::InlineHint, inline_); } // Reads a simple boolean hint of size 0. Receives the code and the field name @@ -5657,6 +5671,10 @@ void WasmBinaryReader::readIdempotentHints(size_t payloadLen) { READ_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent); } +void WasmBinaryReader::readToolchainInlineHints(size_t payloadLen) { + READ_I7_HINT(Annotations::ToolchainInlineHint, toolchainInline); +} + std::tuple WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { auto rawAlignment = getU32LEB(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 8e924414587..9a66bb50fe2 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2863,6 +2863,11 @@ void IRBuilder::applyAnnotations(Expression* expr, assert(func); func->codeAnnotations[expr].idempotent = true; } + + if (annotation.toolchainInline) { + assert(func); + func->codeAnnotations[expr].toolchainInline = annotation.toolchainInline; + } } } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 85c308ea645..15a1e3e6d86 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -90,6 +90,7 @@ const Name InlineHint = "metadata.code.inline"; const Name RemovableIfUnusedHint = "binaryen.removable.if.unused"; const Name JSCalledHint = "binaryen.js.called"; const Name IdempotentHint = "binaryen.idempotent"; +const Name ToolchainInlineHint = "binaryen.inline"; } // namespace Annotations diff --git a/test/lit/passes/strip-toolchain-annotations-func.wast b/test/lit/passes/strip-toolchain-annotations-func.wast index eb80273d70a..c59586e4da9 100644 --- a/test/lit/passes/strip-toolchain-annotations-func.wast +++ b/test/lit/passes/strip-toolchain-annotations-func.wast @@ -42,6 +42,11 @@ (func $idempotent ;; This hint should be removed too. ) -) - + ;; CHECK: (func $test-binaryen-inline (type $0) + ;; CHECK-NEXT: ) + (@binaryen.inline "\00") + (func $test-binaryen-inline + ;; This hint should be removed too. + ) +) diff --git a/test/lit/passes/strip-toolchain-annotations.wast b/test/lit/passes/strip-toolchain-annotations.wast index 1c13cb9b3a9..6c2af951adb 100644 --- a/test/lit/passes/strip-toolchain-annotations.wast +++ b/test/lit/passes/strip-toolchain-annotations.wast @@ -22,6 +22,9 @@ ;; CHECK-NEXT: (call $test ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $test + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (param i32) ;; Inlining hints are not removed, as they are for the VM too. @@ -44,5 +47,9 @@ ;; This should be removed too. (@binaryen.idempotent) (call $test (i32.const 4)) + + ;; And this. + (@binaryen.inline "\00") + (call $test (i32.const 5)) ) ) diff --git a/test/lit/passes/toolchain-inlining.wast b/test/lit/passes/toolchain-inlining.wast new file mode 100644 index 00000000000..cc6cfb9364f --- /dev/null +++ b/test/lit/passes/toolchain-inlining.wast @@ -0,0 +1,168 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --inlining -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (@binaryen.inline "\00") + ;; CHECK-NEXT: (func $never (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (@binaryen.inline "\00") + (func $never (result i32) + (i32.const 42) + ) + + (func $never-unannotated (result i32) + ;; Idential to above, but without the annotation. This will be inlined, + ;; showing the hint works. + (i32.const 42) + ) + + (@binaryen.inline "\7f") + (func $always (result i32) + (local $x i32) + ;; Add code that normally makes us not inline. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br_if $loop + (i32.eqz + (local.get $x) + ) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $always-unannotated (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + (func $always-unannotated (result i32) + (local $x i32) + ;; Idential to above, but without the annotation. This will not be inlined, + ;; showing the hint works. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br_if $loop + (i32.eqz + (local.get $x) + ) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $caller + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $never) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$never-unannotated (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$always$1 (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$always$2 (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (loop $loop0 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop0 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $always-unannotated) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $always-unannotated) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (drop + (call $never) + ) + (drop + (call $never-unannotated) + ) + ;; We need multiple calls here, to avoid the 'single-caller rule that + ;; always inlines anyhow. + (drop + (call $always) + ) + (drop + (call $always) + ) + (drop + (call $always-unannotated) + ) + (drop + (call $always-unannotated) + ) + ) +) diff --git a/test/lit/toolchain-inline-hints-func.wast b/test/lit/toolchain-inline-hints-func.wast new file mode 100644 index 00000000000..363ef512729 --- /dev/null +++ b/test/lit/toolchain-inline-hints-func.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s + +(module + ;; CHECK: (@binaryen.inline "\12") + ;; CHECK-NEXT: (func $func-annotation (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (@binaryen.inline "\12") + (func $func-annotation + ;; The annotation here is on the function. + (drop + (i32.const 0) + ) + ) +) + + diff --git a/test/lit/toolchain-inline-hints.wast b/test/lit/toolchain-inline-hints.wast new file mode 100644 index 00000000000..a61d9ff5392 --- /dev/null +++ b/test/lit/toolchain-inline-hints.wast @@ -0,0 +1,85 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP + +(module + ;; CHECK: (type $func (func)) + ;; RTRIP: (type $func (func)) + (type $func (func)) + + ;; CHECK: (table $table 10 20 funcref) + ;; RTRIP: (table $table 10 20 funcref) + (table $table 10 20 funcref) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $func) + ;; CHECK-NEXT: (@binaryen.inline "\00") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@binaryen.inline "\01") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@binaryen.inline "\7e") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@binaryen.inline "\7f") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: ) + ;; RTRIP: (elem declare func $func) + + ;; RTRIP: (func $func (type $func) + ;; RTRIP-NEXT: (@binaryen.inline "\00") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@binaryen.inline "\01") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@binaryen.inline "\7e") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@binaryen.inline "\7f") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: ) + (func $func + (@binaryen.inline "\00") + (call $func) + (@binaryen.inline "\01") + (call $func) + (@binaryen.inline "\7e") + (call $func) + (@binaryen.inline "\7f") + (call $func) + ;; Unannotated + (call $func) + ) + + ;; CHECK: (func $other-calls (type $func) + ;; CHECK-NEXT: (@binaryen.inline "\12") + ;; CHECK-NEXT: (call_indirect $table (type $func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@binaryen.inline "\34") + ;; CHECK-NEXT: (call_ref $func + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $other-calls (type $func) + ;; RTRIP-NEXT: (@binaryen.inline "\12") + ;; RTRIP-NEXT: (call_indirect $table (type $func) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (@binaryen.inline "\34") + ;; RTRIP-NEXT: (call_ref $func + ;; RTRIP-NEXT: (ref.func $func) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $other-calls + (@binaryen.inline "\12") + (call_indirect (type $func) + (i32.const 0) + ) + (@binaryen.inline "\34") + (call_ref $func + (ref.func $func) + ) + ) +)