// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2023 WebAssembly Community Group participants
// Counterpart: src/passes/NoInline.cpp (5d704ad5); origin remains unconfirmed.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
fn no_inline_no_full_annotation_name() -> String {
"starshine.no-full-inline"
}
///|
fn no_inline_no_partial_annotation_name() -> String {
"starshine.no-partial-inline"
}
///|
pub fn no_inline_summary() -> String {
"Mark named functions as unavailable for full and partial inlining."
}
///|
pub fn no_full_inline_summary() -> String {
"Mark named functions as unavailable for full inlining."
}
///|
pub fn no_partial_inline_summary() -> String {
"Mark named functions as unavailable for partial inlining."
}
///|
pub fn no_inline_pass_pattern(flag : String) -> (String, String)? {
match no_inline_pass_pattern_base(flag) {
Some(name) => {
let pattern = flag.sub(start=name.length() + 1).to_owned()
if pattern.length() > 0 {
Some((name, pattern))
} else {
None
}
}
None => None
}
}
///|
fn no_inline_pass_pattern_base(flag : String) -> String? {
if no_inline_pass_pattern_has_base(flag, "no-inline") {
return Some("no-inline")
}
if no_inline_pass_pattern_has_base(flag, "no-full-inline") {
return Some("no-full-inline")
}
if no_inline_pass_pattern_has_base(flag, "no-partial-inline") {
return Some("no-partial-inline")
}
None
}
///|
fn no_inline_pass_pattern_has_base(flag : String, name : String) -> Bool {
flag.length() > name.length() &&
flag[name.length()] == '=' &&
flag.has_prefix(name)
}
///|
fn no_inline_imported_func_count(mod_ : @lib.Module) -> Int32 {
let mut count = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
_ => ()
}
}
None => ()
}
count
}
///|
fn no_inline_defined_func_count(mod_ : @lib.Module) -> Int32 {
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs.length()
None => 0
}
}
///|
fn no_inline_name_to_string(name : @lib.Name) -> String {
let @lib.Name(text) = name
text.to_owned()
}
///|
fn no_inline_func_names(mod_ : @lib.Module, total : Int32) -> Array[String?] {
let names : Array[String?] = Array::make(total, None)
match mod_.name_sec {
Some(name_sec) =>
match name_sec.func_names {
Some(@lib.NameMap(entries)) =>
for entry in entries {
let @lib.NameAssoc(raw_idx, name) = entry
let idx = raw_idx.reinterpret_as_int()
if idx >= 0 && idx < names.length() {
names[idx] = Some(no_inline_name_to_string(name))
}
}
None => ()
}
None => ()
}
names
}
///|
priv struct NoInlineWildcardProgram {
pattern_length : Int32
word_count : Int32
literal_masks : Array[UInt64]
question_masks : Array[UInt64]
star_masks : Array[UInt64]
fallback_pattern : String?
}
///|
fn no_inline_wildcard_set_bit(words : Array[UInt64], index : Int32) -> Unit {
let word_idx = index / 64
words[word_idx] = words[word_idx] | (1UL << (index % 64))
}
///|
fn no_inline_wildcard_compile(pattern : String) -> NoInlineWildcardProgram {
// Collapsing adjacent stars makes every epsilon closure one step while
// preserving glob semantics.
let mut pattern_length = 0
let mut previous_star = false
let mut needs_fallback = false
for pattern_idx = 0
pattern_idx < pattern.length()
pattern_idx = pattern_idx + 1 {
let is_star = pattern[pattern_idx] == '*'
needs_fallback = needs_fallback ||
(
!is_star &&
pattern[pattern_idx] != '?' &&
pattern[pattern_idx].to_int() > 255
)
if !is_star || !previous_star {
pattern_length += 1
}
previous_star = is_star
}
let word_count = (pattern_length + 64) / 64
let literal_masks = Array::make(256 * word_count, 0UL)
let question_masks = Array::make(word_count, 0UL)
let star_masks = Array::make(word_count, 0UL)
let mut normalized_idx = 0
previous_star = false
for pattern_idx = 0
pattern_idx < pattern.length()
pattern_idx = pattern_idx + 1 {
let pattern_byte = pattern[pattern_idx]
let is_star = pattern_byte == '*'
if !is_star || !previous_star {
let word_idx = normalized_idx / 64
let bit = 1UL << (normalized_idx % 64)
if is_star {
star_masks[word_idx] = star_masks[word_idx] | bit
} else if pattern_byte == '?' {
question_masks[word_idx] = question_masks[word_idx] | bit
} else if pattern_byte.to_int() <= 255 {
let offset = pattern_byte.to_int() * word_count + word_idx
literal_masks[offset] = literal_masks[offset] | bit
}
normalized_idx += 1
}
previous_star = is_star
}
{
pattern_length,
word_count,
literal_masks,
question_masks,
star_masks,
fallback_pattern: if needs_fallback {
Some(pattern)
} else {
None
},
}
}
///|
fn no_inline_wildcard_match_fallback(
pattern : String,
text : String,
visits : Array[Int32],
) -> Bool {
let width = text.length() + 1
let mut previous = Array::make(width, false)
let mut current = Array::make(width, false)
previous[0] = true
for pattern_idx = 0
pattern_idx < pattern.length()
pattern_idx = pattern_idx + 1 {
current[0] = pattern[pattern_idx] == '*' && previous[0]
for text_idx = 1; text_idx <= text.length(); text_idx = text_idx + 1 {
visits[0] += 1
current[text_idx] = if pattern[pattern_idx] == '*' {
previous[text_idx] || current[text_idx - 1]
} else {
(
pattern[pattern_idx] == '?' ||
pattern[pattern_idx] == text[text_idx - 1]
) &&
previous[text_idx - 1]
}
}
let old = previous
previous = current
current = old
}
previous[text.length()]
}
///|
fn no_inline_wildcard_match_compiled(
program : NoInlineWildcardProgram,
text : String,
visits : Array[Int32],
first : Array[UInt64],
second : Array[UInt64],
) -> Bool {
match program.fallback_pattern {
Some(pattern) =>
return no_inline_wildcard_match_fallback(pattern, text, visits)
None => ()
}
for word_idx = 0; word_idx < program.word_count; word_idx = word_idx + 1 {
first[word_idx] = 0UL
second[word_idx] = 0UL
}
no_inline_wildcard_set_bit(first, 0)
if (program.star_masks[0] & 1UL) != 0UL {
no_inline_wildcard_set_bit(first, 1)
}
let mut current = first
let mut next = second
for text_idx = 0; text_idx < text.length(); text_idx = text_idx + 1 {
// Advance every reachable NFA state in machine-word batches. Star states
// consume in place; literal and question states advance by one bit.
let text_unit = text[text_idx].to_int()
let literal_base = if text_unit <= 255 {
text_unit * program.word_count
} else {
-1
}
let mut carry = 0UL
for word_idx = 0; word_idx < program.word_count; word_idx = word_idx + 1 {
visits[0] += 1
let matched = current[word_idx] &
(
program.question_masks[word_idx] |
(if literal_base >= 0 {
program.literal_masks[literal_base + word_idx]
} else {
0UL
})
)
next[word_idx] = (current[word_idx] & program.star_masks[word_idx]) |
(matched << 1) |
carry
carry = matched >> 63
}
carry = 0UL
let mut any_reachable = false
for word_idx = 0; word_idx < program.word_count; word_idx = word_idx + 1 {
let stars = next[word_idx] & program.star_masks[word_idx]
next[word_idx] = next[word_idx] | (stars << 1) | carry
carry = stars >> 63
any_reachable = any_reachable || next[word_idx] != 0UL
}
if !any_reachable {
return false
}
let old = current
current = next
next = old
}
let accept_word = program.pattern_length / 64
let accept_bit = 1UL << (program.pattern_length % 64)
(current[accept_word] & accept_bit) != 0UL
}
///|
fn no_inline_wildcard_match_with_visits(
pattern : String,
text : String,
visits : Array[Int32],
) -> Bool {
let program = no_inline_wildcard_compile(pattern)
no_inline_wildcard_match_compiled(
program,
text,
visits,
Array::make(program.word_count, 0UL),
Array::make(program.word_count, 0UL),
)
}
///|
fn no_inline_wildcard_match(pattern : String, text : String) -> Bool {
no_inline_wildcard_match_with_visits(pattern, text, [0])
}
///|
fn no_inline_annotation_present(
annotations : Array[@lib.FuncAnnotation],
name : String,
) -> Bool {
for annotation in annotations {
if annotation.name == name {
return true
}
}
false
}
///|
fn no_inline_existing_policy_map(
mod_ : @lib.Module,
total : Int32,
) -> Array[Array[@lib.FuncAnnotation]] {
let by_func : Array[Array[@lib.FuncAnnotation]] = []
let mut idx = 0
while idx < total {
by_func.push([])
idx += 1
}
match mod_.func_annotation_sec {
Some(@lib.FuncAnnotationSec(entries)) =>
for entry in entries {
let @lib.FuncAnnotationAssoc(raw_idx, annotations) = entry
let slot = raw_idx.reinterpret_as_int()
if slot >= 0 && slot < by_func.length() {
for annotation in annotations {
if no_inline_policy_annotation_name(annotation) &&
!no_inline_annotation_present(by_func[slot], annotation.name) {
by_func[slot].push(annotation)
}
}
}
}
None => ()
}
by_func
}
///|
fn no_inline_append_policy_associations(
mod_ : @lib.Module,
additions : Array[@lib.FuncAnnotationAssoc],
) -> @lib.Module {
let entries = match mod_.func_annotation_sec {
Some(@lib.FuncAnnotationSec(entries)) => entries.copy()
None => []
}
for association in additions {
entries.push(association)
}
mod_.with_func_annotation_sec(@lib.FuncAnnotationSec::new(entries))
}
///|
fn no_inline_run_module_pass(
mod_ : @lib.Module,
pattern : String,
mark_full~ : Bool,
mark_partial~ : Bool,
) -> @lib.Module {
let total = no_inline_imported_func_count(mod_) +
no_inline_defined_func_count(mod_)
let names = no_inline_func_names(mod_, total)
let by_func = no_inline_existing_policy_map(mod_, total)
let additions : Array[@lib.FuncAnnotationAssoc] = []
let wildcard = no_inline_wildcard_compile(pattern)
let wildcard_visits = [0]
let wildcard_first = Array::make(wildcard.word_count, 0UL)
let wildcard_second = Array::make(wildcard.word_count, 0UL)
let mut changed = false
let mut idx = 0
while idx < total {
// Binaryen assigns every function an internal name. Format the numeric
// fallback only when no name-section entry exists for this function.
let name = match names[idx] {
Some(name) => name
None => idx.to_string()
}
wildcard_visits[0] = 0
if no_inline_wildcard_match_compiled(
wildcard, name, wildcard_visits, wildcard_first, wildcard_second,
) {
let owned : Array[@lib.FuncAnnotation] = []
if mark_full &&
!no_inline_annotation_present(
by_func[idx],
no_inline_no_full_annotation_name(),
) {
let annotation = @lib.FuncAnnotation::new(
no_inline_no_full_annotation_name(),
)
by_func[idx].push(annotation)
owned.push(annotation)
changed = true
}
if mark_partial &&
!no_inline_annotation_present(
by_func[idx],
no_inline_no_partial_annotation_name(),
) {
let annotation = @lib.FuncAnnotation::new(
no_inline_no_partial_annotation_name(),
)
by_func[idx].push(annotation)
owned.push(annotation)
changed = true
}
if !owned.is_empty() {
additions.push(
@lib.FuncAnnotationAssoc::new(idx.reinterpret_as_uint(), owned),
)
}
}
idx += 1
}
if !changed {
return mod_
}
no_inline_append_policy_associations(mod_, additions)
}
///|
///|
fn no_inline_policy_annotation_name(annotation : @lib.FuncAnnotation) -> Bool {
annotation.name == no_inline_no_full_annotation_name() ||
annotation.name == no_inline_no_partial_annotation_name()
}
///|
pub fn no_inline_copy_policy_annotations(
mod_ : @lib.Module,
source_idx : UInt32,
target_idx : UInt32,
) -> @lib.Module {
let total = no_inline_imported_func_count(mod_) +
no_inline_defined_func_count(mod_)
let source = source_idx.reinterpret_as_int()
let target = target_idx.reinterpret_as_int()
if source < 0 || source >= total || target < 0 || target >= total {
return mod_
}
let by_func = no_inline_existing_policy_map(mod_, total)
let additions : Array[@lib.FuncAnnotation] = []
for annotation in by_func[source] {
if no_inline_policy_annotation_name(annotation) &&
!no_inline_annotation_present(by_func[target], annotation.name) {
by_func[target].push(annotation)
additions.push(annotation)
}
}
if additions.is_empty() {
return mod_
}
no_inline_append_policy_associations(mod_, [
@lib.FuncAnnotationAssoc::new(target.reinterpret_as_uint(), additions),
])
}
///|
fn no_inline_policy_flags(mod_ : @lib.Module) -> (Array[Bool], Array[Bool]) {
let total = no_inline_imported_func_count(mod_) +
no_inline_defined_func_count(mod_)
let no_full = Array::make(total, false)
let no_partial = Array::make(total, false)
match mod_.func_annotation_sec {
Some(@lib.FuncAnnotationSec(entries)) =>
for entry in entries {
let @lib.FuncAnnotationAssoc(raw_idx, annotations) = entry
let slot = raw_idx.reinterpret_as_int()
if slot >= 0 && slot < total {
for annotation in annotations {
if annotation.name == no_inline_no_full_annotation_name() {
no_full[slot] = true
} else if annotation.name == no_inline_no_partial_annotation_name() {
no_partial[slot] = true
}
}
}
}
None => ()
}
(no_full, no_partial)
}