惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
C
CERT Recently Published Vulnerability Notes
人人都是产品经理
人人都是产品经理
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
L
LangChain Blog
J
Java Code Geeks
B
Blog
博客园_首页
Engineering at Meta
Engineering at Meta
宝玉的分享
宝玉的分享
D
Docker
L
LINUX DO - 最新话题
Vercel News
Vercel News
aimingoo的专栏
aimingoo的专栏
Microsoft Security Blog
Microsoft Security Blog
Scott Helme
Scott Helme
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Blog of Author Tim Ferriss
U
Unit 42
T
Tenable Blog
F
Fortinet All Blogs
K
Kaspersky official blog
博客园 - 【当耐特】
T
Tailwind CSS Blog
Y
Y Combinator Blog
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Hacker News
The Hacker News
美团技术团队
S
Schneier on Security
P
Proofpoint News Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Proofpoint News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
Cisco Talos Blog
Cisco Talos Blog
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
NISL@THU
NISL@THU
T
The Exploit Database - CXSecurity.com
L
Lohrmann on Cybersecurity
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
I
Intezer
有赞技术团队
有赞技术团队

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром T-TOPS: Как распутать гордиев узел проекта после выхода в прод (меч не понадобится) Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1
glibus · 2026-04-16 · via Все публикации подряд на Хабре

Go-код никогда не исполняется напрямую.

Любая строка Go превращается в машинные инструкции, которые выполняет CPU.

В этой статье мы разберём:

1) Какие инструкции генерирует компилятор Go

2) Как выглядит Go assembler

3) И что на самом деле выполняет процессор

Думаю с обзором и общими положениями мы закончили, поэтому начнем с фундамента – что происходит в CPU когда мы запускаем наше GO-приложение. Конкретно в этой статье разберем основные инструкции, которые может выдать для нашего CPU компилятор Go, что они из себя представляют и как соотносятся с кодом Go. Сразу оговорюсь, что это НЕ гайд по Go Assembler, а разбор того, что из себя представляет Go End 2 End. Попытка докопаться до истины!

Напомню, что сам CPU определяет:

  • Порядок выполнения инструкций

  • Допустимые переупорядочивания(reorder) операций чтения/записи

  • Работу процессорных кэшей

  • Атомарность инструкций

  • Барьеры памяти

Так что CPU – это конечная инстанция исполнения Go-кода.

Reminder

Что вообще понимается под “конечной инстанцией”?

Как известно, процессор не умеет исполнять инструкции различных языков программирования будь то Go, JS, Java… Ему необходим машинный код, спецификация которого завязана на его архитектуру. Сейчас это мы рассматривать не будем, а лишь зафиксируем, что CPU умеет лишь исполнять.

  • load/store

  • Арифметические/логические инструкции

  • branch/jump/call/return

  • атомарные инструкции

  • fence/barrier-подобные эффекты

  • системные переходы, если дошли до syscall/runtime

Будем разбирать это по порядку...

Вводная

Пока нам нужно определение регистра:

Регистр процессора — это небольшой набор ячеек хранения данных, находящийся непосредственно внутри процессорного ядра.

Доступ к регистрам значительно быстрее доступа к памяти. Современные CPU выполняют операции с регистрами практически без задержек по сравнению с обращением к RAM.

Глобально, всё чем мы действительно можем управлять в рамках ASM – это регистры и их байты, а также поток выполнения инструкций. Никаких структур, типов данных и прочего.

Поэтому имеем:

Go

x := 1
x += 1
y = x

ASM

mov rax, 1         ; положить константу 1 в регистр AX
add rax, 1         ; добавить константу 1 к значению в регистре AX
mov [rip + y], rax ; записать содержимое регистра AX в переменную y

Чтобы посмотреть полный вывод можете написать что-нибудь в своем main.go например и использовать команду:

go tool compile -S main.go

Либо что-то в духе:

go build -gcflags="-N -l" -o app main.go
go tool objdump -s "main.main" ./app

Учтите, вы получите команды на Go Assembly!

Если интересно разобраться, то вот ссылка(Не пугайтесь, что там http) на полный перечень команд для x86. Спасибо НГУ. А также серия гайдов по Go Assembler.

Далее последуют примеры для Go Assembler! Это упрощенный код, а не гарантированный вывод компилятора.

Go Assembler

Вообще, интересный факт про Go – он использует свой “сленг” Assembly, который называется Go Assembler.

В чем это вообще выражается?

Это выражается в том, что Go не использует стандартный синтаксис ассемблера архитектуры (GAS/Intel), а вводит собственный слой абстракции над машинными инструкциями. Вот эти абстракции сверху вниз:

1) Псевдорегистры

В Go ASM используются специальные символы, которых нет в реальной ISA процессора.

Go assembler скрывает реальные регистры для:

  • переносимости

  • стабильности ABI(Application Binary Interface)

  • работы линкера

  • генерации stack maps для GC

Конкретнее – вот они

Псевдорегистр

Назначение

SB

symbol base. global/static symbols, туда идут глобальные переменные.

SP

stack pointer. Псевдорегистр, обозначающий вершину текущего stack frame.

FP

frame pointer. Псевдоуказатель на область аргументов функции).

PC

program counter. Адрес следующей инструкции для выполнения.

А настоящие CPU-регистры:

AX BX CX DX SI DI BP R8–R15

То есть

MOVQ AX, main.y(SB)   ; GoASM
mov [rip+offset], rax ; x86 ASM

2) Модифицированная система адресации

В Go assembler используются формы:

name(SB)

x-8(SP)

arg+0(FP)

MOVQ arg+0(FP), AX ; Прочитать аргумент функции

3) Декларация функций

TEXT main.main(SB), ABIInternal, $0-8

То есть

функция main.main

ABIInternal – внутренний ABI компилятора Go. Он используется самим компилятором и runtime и может меняться между версиями языка.

stack frame size

4) Метаданные для runtime

Go assembler содержит директивы, которые вообще не имеют отношения к CPU, но нужны runtime.

Например:

FUNCDATA

PCDATA

5) Встроенный в синтаксис ABI

Например ABIInternal, то есть используется внутренний ABI Go.

6) Архитектурная независимость и символы(например SB) вместо конкретных адресов

А зачем нам вообще Go Assembler?

Можно ограничиться общими словами про архитектурную независимость и интеграцию с GC, но посмотрим на это с практической стороны.

У Go есть runtime — большой системный слой, который управляет goroutine, стеком, GC и переходами в системные вызовы. Часть этого кода неизбежно пишется на ассемблере.

Если бы Go Assembler не существовал, то runtime пришлось бы писать отдельно под каждую архитектуру, используя её собственный синтаксис ассемблера (x86, ARM, RISC-V и т.д.). Это сильно усложнило бы сопровождение и поддержку новых платформ.

Go Assembler решает эту проблему, вводя единый синтаксический слой, который понимает компилятор и линкер. В нём используются абстракции вроде SB, SP, FP, а также директивы runtime (TEXT, FUNCDATA, PCDATA).

В итоге runtime может работать с логической моделью Go (stack frame, символы, метаданные GC), а архитектурно-зависимые детали изолируются в небольших asm-файлах для конкретных платформ.

Go Load-n-Store

Есть смысл начать с самого простого – чтения и записи переменных. 

На самом деле, в ASM это происходит по очень незамысловатой схеме:
1) Использование регистров. О них мы знаем из спецификации конкретной архитектуры процессора, то есть "предустановлены".

2) Перемещение данных между регистрами и памятью.

Соответственно, рассмотрим код

x := 1
y = x

После оптимизаций компилятор может упростить его до:

MOVQ $1, AX        ; загрузить константу 1 в регистр AX
MOVQ AX, main.y(SB); записать значение из AX в глобальную переменную y

Однако если отключить оптимизации (-N -l), код будет ближе к исходной логике программы:

MOVQ $1, main.x-8(SP) ; записать константу 1 в локальную переменную x
MOVQ main.x-8(SP), AX ; load: прочитать значение x из памяти в регистр AX
MOVQ AX, main.y(SB)   ; store: записать значение регистра AX в переменную

Я думаю, что у человека это читающего возникнет вопрос:

MOVQ – это и есть load/store?

MOVQ — это инструкция перемещения данных.

Буква Q означает quadword (8 байт) — размер операнда.

Есть также B(1), W(2), L(4)

Ремарка

На 64-битных архитектурах (amd64) инструкция MOVQ копирует 8-байтовое значение за одну операцию.

На 32-битных архитектурах (386) такой инструкции нет, поэтому компилятор разбивает копирование 64-битного значения на две операции MOVL по 4 байта.

Размер инструкции соответствует размеру данных, с которыми работает процессор. Кстати, в перспективе мы сможем понять, откуда пошло выравнивание структур в том числе и с помощью этого.

Сама инструкция MOV может выполнять разные виды копирования:

immediate -> register

register -> register

register -> memory

memory -> register

Load и Store – это более верхнеуровневые концепции, которые означают:

load = чтение из памяти в регистр;

store = запись из регистра в память.

Замечу, что с самом Go Load и Store трактуются наоборот, потому что мы рассматриваем эти операции не относительно CPU, а относительно самой программы.

Но возникает закономерный вопрос: а что будет, если мы запишем, скажем, структуру на 16 байт, если у нас максимум только 8(MOVQ)?

type Point struct {
	x int
	y int
}

func main() {
	p1 := Point{1, 2}
	p2 := p1
}
// int = 8 bytes
// Point = 16 bytes

p1 := p2

Это копирование структуры!

p2.x = p1.x

p2.y = p1.y

То есть копируются все поля. И одной операцией MOVQ мы не обойдемся.

Поэтому компилятор выдаст нам что-то в духе:

MOVQ main.p1(SP), AX   ; load в регистр AX p1.x
MOVQ AX, main.p2(SP)   ; store в память из AX p1.x(запись в p2.x)

MOVQ main.p1+8(SP), AX ; load в регистр AX p1.y(отступ как раз 8 байт, то есть следующей записи в регистр)
MOVQ AX, main.p2+8(SP) ; store в память в p2.y из AX(также с учетом отступа)

То есть 4 операции

И напоследок в этом разделе – как себя ведет запись/чтение указателей.

Зафиксируем: указатель в Go — это просто адрес в памяти.

Поэтому операции с указателями на уровне ASM обычно сводятся к копированию адресов и разыменованию.

Go:

x := 10
p := &x

p2 := p
*p = 20

ASM:

LEAQ main.x(SP), AX  ; Load Effective Address x в регистр AX
MOVQ AX, main.p(SP)  ; store из AX в переменную p в памяти

MOVQ main.p(SP), AX  ; load p в регистр AX
MOVQ AX, main.p2(SP) ; store из AX в p2

MOVQ main.p(SP), AX  ; load указателя p в AX
MOVQ $20, (AX)       ; записать значение 20 по адресу, на который указывает p

Я думаю, с самым простым мы закончили...

Go в арифметику(и логику)

Следующая великая миссия нашего железа – обработка данных, собственно не зря компьютер – это компьютер. В общем случае, набор арифметических и логических команд определяется ISA процессора. Но Go Assembler имеет те же арифметико-логические инструкции, что и x86.

Давайте по порядку(примем допущения, что наши переменные x и y в регистрах для каждой отдельно операции).

Сложение и вычитание

Go:

z := x + y
z1 := x - y

ASM:

ADDQ BX, AX ; AX = AX + BX (z)
MOVQ AX, CX ; сохранить результат
            ; Представим что где вычитание не менялся AX
SUBQ BX, AX ; AX = AX - BX (z1)
MOVQ AX, DX ; сохраняем результат

Умножение и деление

Go:

z := x * y
z1 := x / y

ASM:

IMULQ BX, AX ; AX = AX * BX
MOVQ AX, CX  ; store в z

CQO      ; расширить знак AX в DX, Convert Quadword to Octoword нужен для корректного деления знаковых чисел. 
         ; В DX сохраняем -1, в AX наше число. Например-5
IDIVQ BX ; AX = (DX:AX) / BX, DX = остаток

В Go assembler знаковое деление на amd64 также опирается на правила x86-64. Деление в x86 отличается от других арифметических операций. Инструкция IDIV использует фиксированные регистры DX:AX как делимое. После выполнения деления частное записывается в AX, а остаток — в DX.

Да, и внимательный читатель мог заметить, что деление – дороговатая операция на самом деле, поэтому старайтесь избегать его по возможности, хотя в узких случая компилятор может его оптимизировать например до битового сдвига.

AND, OR, XOR

Go:

z := x & y
z1 := x | y
z2 := x ^ y

ASM:

ANDQ BX, AX ; AX = AX & BX
MOVQ AX, CX ; z

ORQ BX, AX  ; AX = AX | BX
MOVQ AX, DX ; z1

XORQ BX, AX ; AX = AX ^ BX
MOVQ AX, SI ; z28-

Сдвиги влево/вправо

Go:

z := y << x
z1 := y >> x

ASM:

MOVQ AX, CL ; CL = количество сдвигов

SHLQ CL, BX ; BX = BX << CL
MOVQ BX, DX ; z

SHRQ CL, BX ; BX = BX >> CL
MOVQ BX, SI ; z1

Сравнение

Go:

x < y

ASM:

CMPQ BX, AX ; сравнить AX и BX
JLT less    ; перейти если AX < BX, JLT пока не трогаем

CMPQ не сохраняет результат сравнения в регистр, а только выставляет флаги процессора.

Test

if x == 0

ASM:

TESTQ AX, AX ; проверить AX == 0
JEQ zero ; переход если ноль

Чтобы вас не путать объясню: TEST – это битовое AND значения с самим с собой, поэтому 0 тут может дать только… 0!

Помимо Q(8) все так же есть B(1), W(2), L(4).

Go управлять потоком

Итак, я в самом начале упоминал, что помимо байтов и регистров мы также можем управлять потоком выполнения. Вообще, что это значит?

Процессор выполняет какую-то последовательность инструкций, что, собственно и является потоком выполнения и задание этой самой последовательности и есть управление потоком выполнения.

В контексте управления потоком выполнения мы можем использовать следующие операции:

jump – безусловный переход(goto)

branch – условное изменение хода выполнения(аналог if)

call – вызов функции

return – возврат из функции

Всё это в конечном счёте сводится к изменению PC: процессор либо идёт к следующей инструкции, либо прыгает в другое место. В Go assembler это описывается теми же базовыми инструкциями перехода и вызова, что и в обычном asm для целевой архитектуры.

Jump

По факту это переход на определенную строчку кода. Я думаю, что уже вырисовывается картинка, как из этого может получиться цикл. JMP происходит к определенной метке в коде. Пример ниже

lbl: MOVQ $1, AX ; помещаем константу 1 в регистр AX
                 ; условимся начать отсюда
JMP lbl          ; переход к lbl. 
                 ; PC = address(label), то есть ставим указатель следующей инструкции на метку lbl

По сути это основа для:

  • хвостов функций

  • циклов

  • пропуска кусков кода

  • переходов внутри runtime

Branch

branch — это условный переход.

Обычно он идёт после CMP или TEST, которые выставляют флаги CPU. Возьмем пример с CMPQ выше:

CMPQ BX, AX ; сравнить AX и BX
JLT less    ; если AX < BX, перейти к метке less

И я уверен, что вы уже поняли, что с точки зрения Go Assembler представляют из себя ветвления!

Замечу, что J* — не одна конкретная инструкция, семейство инструкций. С ним вы подробнее можете ознакомиться в официальной документации.

Call

Здесь уже несложно догадаться, что это вызов подпрограммы(функции).

Выглядит это в коде так

CALL	·panicUnaligned(SB) 

Пример взял из этого файла

Но по сути означает следующее:

1) Сохранить адрес следующей инструкции

2) Перейти в начало вызываемой функции

В Go assembler вызов функции записывается через символ, например runtime·abort(SB), а реальный адрес подставляет линкер. Это обычный паттерн в исходниках runtime.

Отдельно отмечу, что

CALL -> сохраняет return address

JMP  -> просто прыгает

Return

RET – возврат из функции. Думаю, все прекрасно представляют, что это может означать, но давайте разберемся. Работает это так:

  1. Взять сохранённый адрес возврата

  2. Передать управление обратно

Соответственно, CALL и RET – парные инструкции, так же как () и return.

А теперь на последок в этой части давайте разберемся с этими инструкциями. Пусть есть код:

package main

func add(a, b int) int {
	return a + b
}

func main() {
	x := add(2, 3)
	_ = x
}

Как это понимать логически:

main.main

CALL main.add

1) Получить результат

2) Сохранить

main.add

1) Загрузить аргументы

2) Сложить

3) Вернуть результат

Тогда на уровне компилятора получим примерно:

TEXT main.add(SB), ABIInternal, $0-24 ; TEXT - нотация для функций, а 0-24 – stack frame для двух аргументов и возврата. ABI я осознанно не трогаю

MOVQ a+0(FP), AX    ; AX = a
MOVQ b+8(FP), BX    ; BX = b
ADDQ BX, AX         ; AX = a + b
MOVQ AX, ret+16(FP) ; вернуть результат

RET

TEXT main.main(SB), ABIInternal, $0-16

MOVQ $2, AX
MOVQ AX, 0(SP) ; первый аргумент

MOVQ $3, AX
MOVQ AX, 8(SP) ; второй аргумент

CALL main.add(SB) ; вызов функции

MOVQ 16(SP), AX ; получить результат
MOVQ AX, main.x(SB)

RET

По итогу мы разобрали наверное самое базовое и, вероятно, неинтересное в глубинах Go, но это база для того, чтобы понимать происходящее далее в том числе атомарные операции и то, как вообще происходит исполнение Go.

В заключение напомню – ЭТО НЕ ГАЙД ПО GO ASSEMBLER, а разбор того, что из себя представляет End 2 End язык Go.

Мы разобрали базовые типы инструкций, через которые CPU выполняет код: арифметику, сравнение, переходы и вызовы функций.

В следующей части посмотрим на более интересные вещи:

  • атомарные инструкции

  • fence/barrier-подобные эффекты

  • системные переходы

UPD: Я натуральный орк, конечно. Хотел сразу перейти к темам без проблем, которые они решают вот статья про многозадачность, многопоточность, кэши и проблемы, порождаемые этим.

P.S. Я думаю, что нет смысл забуриваться далее и разбирать типы триггеров, используемых в CPU, RAM :)