Потому что в моем понимании, и я думаю, что это правильно, низкоуровневость ЯП - это не только доступ к железу, а ещё и относительно прямая сопоставимость написанного с маш.кодом конечной архитектуры, без всякой доп.магии. И надо разделять ЯП и компилятор/транслятор и пр. То что вы "А" сопоставимое для "а" транслировали в "Б" сопоставимое с "б", не делает "А" прямо сопоставимым с "б".
Например в "А" - 1 мнемоника описывает что-то и соответствует своему маш.коду на "а", тогда как транслировав в "Б" (даже если все так же 1 мнемоника, а может же быть и несколько) - в "б" совершенно другой маш.код и возможно аргументы по другому стоят, следовательно даже если избежать этапа перегонки в "Б" и сразу лепить маш.код для "б", то вам нужно для этого делать магию, что уже ломает кросс и получается низкоуровневость с точки зрения конечной архитектуры, так как нет гарантии прямой сопоставимости "А" с "б", большая вероятность того, что это вообще не будет работать, т.е это бред просто для нее, если вы не контролируете некоторые аспекты, например у архитектуры может просто не быть векторных регистров и что теперь, во что вы транслируйте хоть сколько-то напоминающее оригинал, поэтому даже доступ к железу (в таком виде) с моей точки зрения - это ещё не всё чтобы быть низкоуровневым, потому что возможно вы его получили через прослойки(vm например)/трансляции всякого рода одного в другое и т.п, это ещё при условии, что вы вообще сможете сделать такую трансляцию.
Поэтому не может быть КроссАссемблера для архитектур, может быть VM которая работает на разных архитектурах, но ассемблер (например LLVM IL), который в ней крутится относится к ней самой, т.е к конкретной архитектуре самой VM, а не к архитектуре на которой VM крутится или к какой-то еще.
Кароче философия уже пошла.