Lowent 매뉴얼←↑→

30 하드웨어 — 레지스터, 인터럽트, 기계 명령

먼저 알아야 할 것

13장 이름 붙인 타입 · layout·view 와 칸의 닿는 법 표지
20장 할당기와 고정 메모리 · 운영체제 없는 기계의 고정 창
29장 C 와 만나는 자리 · 검사할 수 없는 자리는 표시·권리·효과 줄로 가둔다

돌아보기

29장에서 C 를 부르는 op 이 갖춰야 하는 셋은 무엇이었고, 각각 누구에게 말하는 것이었는가?

답. unsafe 표시(사람에게), cap c(처리기에게), 효과 줄(부르는 쪽에게)이었다. 이 장의 장치 레지스터와 기계 명령도 같은 방식으로 가둔다. 권한의 이름과 효과의 이름만 달라진다.

이 장의 필요성과 맥락

시스템 언어의 가장 낮은 자리는 운영체제 없이 장치 레지스터를 직접 읽고 쓰는 펌웨어다. 이 자리의 결함은 조용하고 치명적이다. 컴파일러가 “쓸모없어 보이는” 레지스터 읽기를 지우거나, 쓰기 전용 레지스터를 읽어 장치를 움직이거나, 인터럽트 처리기를 보통 함수처럼 불러 틀린 스택에서 돌린다. Lowent 는 이것들을 번역에서 막는다. 앞의 장들에서 세운 도구 — 권한·효과·배치· 고정 창 — 이 하드웨어에서 어떻게 쓰이는지 모은다.

이 장이 끝나면

mmio <주소> 를 붙인 구조체로 장치의 레지스터 지도를 적고, read_volatile·write_volatile 로 닿는 법을 익힌다. cap mmio 와 device 효과, 읽기 전용·쓰기 전용 표지가 번역에서 강제된다는 것, 장치를 값으로 받을 수 없는 이유를 알게 된다. vector 절로 인터럽트 처리기를 만들고, build tier 로 기계가 감당하는 효과를 정하며, asm 절로 기계 명령을 가두어 쓰는 법도 보게 된다.

이 장에서 답할 질문

  1. C 에서는 volatile 한정자를 붙인다. 무엇이 다른가?

30.1 레지스터 지도#

examples/ch30/gpio.low

module gpio_demo .
rem run: drive 0 [0,0,0,0,7,0,0,0,0,0,0,0]

build tier t1 .

struct gpio do
  mmio 0x40020000 .
  moder u32 rw .
  idr u32 ro .
  bsrr u32 wo .
end

unsafe proc drive
  input dev cap mmio .
  input regs mut slice u8 .
  output u32 .
  effects device unsafe .
do
  var g gpio be view gpio regs .
  write_volatile g moder 2 .
  write_volatile g bsrr 1 .
  return read_volatile g idr .
end

실행 결과

$ lowentc --run drive gpio.low 0 [0,0,0,0,7,0,0,0,0,0,0,0]
drive(0, [2,0,0,0,7,0,0,0,1,0,0,0]) = 7
  arg1 (written) = [2,0,0,0,7,0,0,0,1,0,0,0]

VM 에서는 이것이 실제로 돈다. 장치 대신 호출자가 건넨 바이트 버퍼가 레지스터 자리가 되고, view gpio regs 가 그 위에 지도를 얹는다. 실행 결과의 인자 모습 [2,0,0,0,7,0,0,0,1,0,0,0] 에서 moder 에 2, bsrr 에 1 이 쓰였고 idr 의 7 이 읽혀 돌아온 것을 볼 수 있다. 실행 인자의 첫 0 은 cap mmio 자리를 채우는 자리표다.

지도와 버퍼를 겹쳐 그리면 이렇다. 칸이 선언 차례로 4 바이트씩 놓인다.

 주소         칸     닿는 법  VM 에서 (u32 하나 = 4 바이트)
 0x40020000   moder  rw       [ 2 0 0 0 ]  ◀── write_volatile g moder 2
 0x40020004   idr    ro       [ 7 0 0 0 ]  ──▶ read_volatile g idr = 7
 0x40020008   bsrr   wo       [ 1 0 0 0 ]  ◀── write_volatile g bsrr 1

문. C 에서는 volatile 한정자를 붙인다. 무엇이 다른가?

답. C 의 volatile 은 변수의 성질이고 잊기 쉽다. 붙이지 않으면 최적화기가 읽기를 지워도 경고가 없다. Lowent 에서는 레지스터에 닿는 일이 read_volatile·write_volatile 이라는 이름 붙은 연산이고, 그 연산을 쓰는 op 의 머리에 device 효과와 cap mmio 가 선다. 읽기를 지워도 되는지는 변수에 붙은 한정자를 기억하는 일이 아니라 연산의 이름이 정한다.

30.2 닿는 법은 번역에서 강제된다#

읽기 전용 레지스터에 쓰면 거절된다.

examples/ch30/ro_write.low

module ro_write .
rem expect: E-MMIO-PERM

struct gpio do
  mmio 0x40020000 .
  moder u32 rw .
  idr u32 ro .
end

unsafe proc poke input dev cap mmio . input regs mut slice u8 . output u32 . effects device unsafe .
do
  var g gpio be view gpio regs .
  write_volatile g idr 1 .
  return 0 .
end

실행 결과

$ lowentc --check ro_write.low
ro_write.low:13:0 E-MMIO-PERM: this register is READ-ONLY (`ro`) — writing it is a compile error, not a runtime surprise (RFC-0042 D3). The device says what it will accept; the type says it back

쓰기 전용 레지스터를 읽는 것도 같다. 쓰기 전용 레지스터를 읽으면 쓰레기가 나오거나 읽는 행위가 장치를 움직인다. 그 읽기는 쓸모없는 것이 아니라 틀린 것이다. 장치가 무엇을 받아들이는지 말했으면 타입이 그것을 되말한다.

장치를 값으로 받을 수도 없다.

examples/ch30/byvalue.low

module byvalue .
rem expect: E-MMIO-BYVALUE

struct gpio do
  mmio 0x40020000 .
  moder u32 rw .
end

unsafe proc setup input dev cap mmio . input g gpio . output u32 . effects device unsafe .
do
  write_volatile g moder 2 .
  return 0 .
end

실행 결과

$ lowentc --check byvalue.low
9:0 E-MMIO-BYVALUE: an `mmio` register block cannot be a by-value parameter. A struct parameter is COPIED, and copying a device means reading EVERY register at once — including the `wo` ones, whose reads D3 promises to reject at compile time, at a moment the program never wrote. A register block is a DEVICE, not a value: pass the view instead (RFC-0042 D3 · §8-4)

구조체 매개변수는 복사된다. 장치를 복사한다는 것은 쓰기 전용까지 포함한 모든 레지스터를 한꺼번에 읽는다는 뜻이고, 복사본에 쓰는 일은 장치에 닿지 않는다. 그렇게 쓰인 프로그램은 아무 일도 하지 않으면서 아무 말도 하지 않는다. 그래서 번역에서 막는다. 레지스터 묶음은 슬라이스에 view 로 얹어 다룬다. 운영체제가 있는 기계에서 절대 주소를 열려고 하면 E-MMIO-NOHOST 로 거절된다 — 그 주소는 장치가 아니다.

30.3 인터럽트 처리기#

op 에 vector <번호> . 절을 붙이면 그 op 은 인터럽트 처리기가 된다. 급함은 priority <수> . 로 적는다.

examples/ch30/isr.low

module isr .
rem flags: --target cortex_m

proc on_exti
  vector 6 .
  priority 2 .
  output void .
  effects device .
do
  return .
end

실행 결과

$ lowentc --check isr.low
== check: ok ==

이 예제는 --target cortex_m 으로 검사한다. 인터럽트 처리기는 기계가 부르므로 넷을 지킨다.

지킬 것어기면왜
아무도 부르지 않는다E-ISR-CALLED우리 코드가 부르면 틀린 스택·틀린 우선순위에서 돈다
매개변수가 없다E-ISR-PARAMS기계는 인자를 건네지 않는다
돌려주는 것이 없다E-ISR-OUTPUT돌려줄 상대가 없다
effects device 를 적는다E-ISR-EFFECT장치 때문에 있는 자리다

표 30.1 — 인터럽트 처리기가 지켜야 하는 것

examples/ch30/isr_called.low

module isr_called .
rem expect: E-ISR-CALLED

proc on_exti
  vector 6 .
  output void .
  effects device .
do
  return .
end

proc impatient output void . effects device .
do
  on_exti .
end

실행 결과

$ lowentc --check isr_called.low
isr_called.low:14:0 E-ISR-CALLED: nobody calls an interrupt handler — the HARDWARE does. Calling it from Lowent code runs it on the wrong stack, at the wrong priority, with interrupts in the wrong state (RFC-0042 D5)

인터럽트 처리기와 보통 코드가 나누어 쓰는 상태는 우선순위와 큐의 규율로 오간다. 그 규율은 언어가 아니라 라이브러리의 일이다. 락 없이 한 생산자와 한 소비자가 값을 넘기는 spsc 링 버퍼가 그 자리에 쓰인다(34장).

30.4 기계가 감당하는 것 — build tier#

build tier <이름> . 은 이 기계가 무엇을 감당하는가를 말한다. 등급마다 쓸 수 있는 효과가 정해져 있다.

등급어떤 기계새로 감당하는 효과
t0아주 작은 기계, 운영체제 없음none unsafe panic state wait cancel
t1작은 기계위에 더해 io device
t2실시간 운영체제위에 더해 alloc lock atomic blocking concurrent
t3운영체제가 있는 기계위에 더해 heap page_fault detach — 곧 전부

표 30.2 — 등급과 감당하는 효과

examples/ch30/tier.low

module tier .
rem expect: E-TIER-EFFECT

build tier t1 .

proc scratch input al cap allocator . output u64 . effects alloc .
do
  let g option mut slice u8 be alloc_bytes al capacity 16 .
  guard is_some g . else return 0 .
  return len (some_value g) .
end

실행 결과

$ lowentc --check tier.low
tier.low:6:0 E-TIER-EFFECT: this effect is not available at the declared concurrency TIER. The tier says what the HARDWARE CAN CARRY — a thread pool on an ATtiny is not a runtime problem, it is a COMPILE ERROR (RFC-0039 D4). And because effects PROPAGATE along the call chain, the refusal lands where you CALL it, not at link time

t1 기계에서 할당(alloc)은 감당하지 못한다고 선언했으므로 거절된다. 이것은 실행 중에 느려지는 문제가 아니라 애초에 그 기계에 실을 수 없는 것이다. 효과는 부르는 사슬을 따라 올라오므로, 거절은 효과가 처음 생긴 자리가 아니라 부르는 자리에서도 난다. 등급을 적지 않으면 t3 이고 아무것도 막지 않는다.

등급(무엇을 감당하는가)과 프로파일(25장 — 어떤 동시성 짜임을 쓰는가)은 다른 축이다. 운영체제가 있는 작은 기계도 있고, 없는 큰 기계도 있다.

흔한 오해. --target cortex_m 을 주면 등급도 저절로 정해진다

대상(--target)은 기계어와 machine.* 상수(힙이 있는가 따위)를 정하고, 그래서 힙은 대상만으로도 거절된다 (18장). 등급은 소스에 적는 약속이다. 같은 대상이라도 t0 를 적으면 입출력까지 막을 수 있다. 약속을 적은 사람만 그 약속을 지고, 처리기는 적지 않은 약속을 지어내지 않는다.

30.5 기계 명령 — asm#

특권 명령, 시스템 호출, 정확한 사이클 수처럼 이식 가능한 코드로 닿지 못하는 것이 있다. 그 자리에서 op 의 몸을 기계 명령으로 쓴다.

examples/ch30/asm.low

module asm_add .

unsafe proc add_native
  input k cap machine .
  input a u64 .
  input b u64 .
  output u64 .
  effects unsafe .
  asm x86_64 .
    reg a .
    reg b .
    out reg r .
    clobber flags .
    options pure nomem nostack .
do
  text ASM
    mov {a}, {r}
    add {b}, {r}
  ASM
end

실행 결과

$ lowentc --check asm.low
== check: ok ==

처리기는 템플릿 안을 읽지 못한다. 그러나 피연산자 목록과 템플릿의 {이름} 은 같은 것을 말하는 두 표현이므로 양쪽으로 맞대어 검사한다.

examples/ch30/asm_unbound.low

module asm_unbound .
rem expect: E-ASM-UNBOUND

unsafe proc add_native
  input k cap machine .
  input a u64 .
  input b u64 .
  output u64 .
  effects unsafe .
  asm x86_64 .
    reg a .
    reg b .
    out reg r .
    clobber flags .
do
  text ASM
    mov {a}, {r}
    add {c}, {r}
  ASM
end

실행 결과

$ lowentc --check asm_unbound.low
asm_unbound.low:4:0 E-ASM-UNBOUND: the template names an operand the `asm` clause never declared — the assembler would either reject it or, worse, read WHATEVER register happens to be there

선언하지 않은 {c} 를 템플릿이 부르면 어셈블러는 그것을 거절하거나, 더 나쁘게는 그 자리에 있던 레지스터를 그냥 읽는다. 반대로 선언한 피연산자를 템플릿이 쓰지 않아도 거절된다(E-ASM-UNUSED). options pure 는 “곁효과가 없다” 는 약속이라 처리기가 그것을 믿고 호출을 지우거나 합칠 수 있는데, 효과 줄이 입출력이나 장치를 만진다고 말하면 둘 다 참일 수 없으므로 거절된다 (E-ASM-OPTLIE). VM 은 기계 명령을 돌리지 못하고 못 돌린다고 말한다(E-VM-ASM). 네이티브에서만 돈다.

30.6 흡수 경계 — unsafe 가 멈추는 자리#

앞 절의 가둠 넷에는 대가가 하나 딸려 온다. effects unsafe 는 부름을 따라 끝까지 번진다. 어셈블리 잎을 한 줄 쓰면 그것을 부른 op 도, 그 op 을 부른 op 도 차례로 unsafe 를 적어야 한다. 표준 라이브러리에서 기계 명령이 한 번도 안 쓰인 까닭이 그것이다(2026-09-23 실측: lib/ 예순네 모듈에서 asm 사용 0).

번짐 자체는 옳다. 처리기가 못 보는 일을 부르는 쪽이 모르면 효과 줄은 거짓말이 된다. 빠진 것은 책임을 넘겨받는 자리였다.

export proc add2 input a u64 . input b u64 . output u64 . effects none .
  absorbs machine k .          rem 여기서 멈춘다. `k` 가 이 몸 안의 `cap machine` 이다
  reference add2_soft .        rem 같은 답을 내야 하는 순수한 판
  why "레지스터 둘을 더할 뿐 메모리를 만지지 않는다(options pure nomem nostack)." .
  requires ge a 0 .
do
  return asm_add2 k a b .
end

부름 사슬로 그리면 차이가 이렇다.

 흡수 없이                                 흡수와 함께
 main      effects unsafe  ▲               main      (적을 것 없음)
 add2      effects unsafe  │ 위로 번진다   add2      absorbs machine k ◀ 여기서 멈춘다
 asm_add2  effects unsafe  │               asm_add2  effects unsafe

갖추지 않으면 거절된다. 효과 줄이 none 이 아니면 E-ABSORB-IMPURE, 참조 구현이 없으면 E-ABSORB-NOREF, 계약(requires)이 없으면 E-ABSORB-NOCONTRACT, 근거(why)가 비었으면 E-ABSORB-NOWHY 다.

누가 흡수해도 되는가는 매니페스트가 정한다. 흡수는 «사람이 보증한다» 는 말이므로, 그 권리를 원하는 소스가 스스로에게 줄 수 없다. pkg.low 에 build absorb <모듈> . 로 적힌 모듈만 쓸 수 있고, 적히지 않으면 E-ABSORB-PLACE 다. 매니페스트가 아예 없으면 아무도 흡수하지 못한다.

도구가 그 자리를 말한다. lowentc --absorbs f.low 는 어느 op 이 무엇을 흡수했고 근거가 무엇인지 한 줄씩 낸다. 못 보는 구역은 숨기는 것이 아니라 보이게 둔다.

  add2                      absorbs machine as `k`  ref=add2_soft  line 40
      why: 레지스터 둘을 더할 뿐 메모리를 만지지 않는다…
absorbs: 1 op(s) stop `unsafe` here

타이밍은 적어 두는 것이다. 값에 따라 시간이 달라지는지는 도구가 확인하지 못한다. 그래서 흡수 장부의 타이밍 칸에 사람이 적고, 비워 두면 검사가 통과시키지 않는다 — «모른다» 도 답이다.

왜 참조 구현을 요구하나

같은 답을 내는 순수한 판이 없으면 «옳음» 과 «일관되게 틀림» 을 가를 길이 없다. 실제로 이 저장소에서 그 일이 있었다: 기계 명령으로 다시 쓴 GHASH 가 봉인하고 다시 여는 시험을 통과했는데, 곱셈이 통째로 다른 연산이었다. 봉인과 개봉이 같은 코드를 쓰므로 스스로 일관되기만 하면 왕복은 성립한다. 잡은 것은 셈 판과의 대조였다.

30.7 기계가 가진 것을 쓸까 — --hw#

기계 명령을 쓸지는 짓는 사람이 정한다.

고르는 것무엇이 일어나나
--hw none(기본)모든 셈을 평범한 코드로. 어느 기계에서나 선다
--hw pclmul,aes,sse2,avx2그 명령이 있다고 보고 낸다. 없는 기계에서는 돌지 않는다
--hw auto전부 담고 시작할 때 한 번 골라 고정한다. 어디서나 서고, 있는 기계에서는 빠르다. VAES·VPCLMULQDQ(ymm 한 칸에 두 블록)까지 담는다 — 처음 쓸 때 AES-NI 판과 맞대 보고 다르면 쓰지 않는다

표 30.3 — --hw 가 고르는 것

담는 것은 셈을 대신하는 명령만이 아니다. sse2 · avx2 가 주는 것은 폭이다 — 서로 독립인 일 넷·여덟을 한 레지스터에 나란히 싣는 자리이고, ChaCha20 의 블록들이 바로 그런 모양이다. 규칙은 같다: 답은 같고, 없는 기계에는 담지 못하며, VM 은 언제나 평범한 코드로 돈다.

30.8 흔한 실수#

반례. 쓰기 전용 레지스터를 읽어 방금 쓴 값을 확인한다

examples/ch30/mistake_woread.low

module mistake_woread .
rem expect: E-MMIO-PERM

struct gpio do
  mmio 0x40020000 .
  moder u32 rw .
  idr u32 ro .
  bsrr u32 wo .
end

unsafe proc last_set input dev cap mmio . input regs mut slice u8 . output u32 . effects device unsafe .
do
  var g gpio be view gpio regs .
  rem ✘ 방금 무엇을 썼는지 확인하려고 쓰기 전용 레지스터를 읽는다
  return read_volatile g bsrr .
end

실행 결과

$ lowentc --check mistake_woread.low
mistake_woread.low:15:0 E-MMIO-PERM: this register is WRITE-ONLY (`wo`) — reading it is a compile error. A wo register often reads as garbage (or has a read side effect), so the read is not merely useless: it is wrong

보통 변수라면 쓰고 읽어 확인하는 것이 좋은 습관이지만, 쓰기 전용 레지스터를 읽으면 쓰레기가 나오거나 읽는 행위가 장치를 움직인다. 그래서 E-MMIO-PERM 이다. 방금 쓴 값이 필요하면 쓰기 전에 지역 변수에 남겨 두고, 장치의 실제 상태는 데이터시트가 정한 읽기 레지스터 (여기서는 idr)로 확인한다.

반례. 인터럽트 처리기에 매개변수를 둔다

examples/ch30/mistake_isrparams.low

module mistake_isrparams .
rem flags: --target cortex_m
rem expect: E-ISR-PARAMS

rem ✘ 몇 번 핀이 울렸는지 인자로 받으려 한다 --- 기계는 인자를 건네지 않는다
proc on_exti
  vector 6 .
  input pin u32 .
  output void .
  effects device .
do
  return .
end

실행 결과

$ lowentc --check --target cortex_m mistake_isrparams.low
mistake_isrparams.low:6:0 E-ISR-PARAMS: an interrupt handler takes NO parameters — the HARDWARE calls it, and hardware does not pass arguments. Shared state goes through the priority/queue discipline (RFC-0039 D1c), not through a parameter list

기계는 처리기를 부를 때 인자를 건네지 않는다. 어느 핀이 울렸는지는 처리기 안에서 장치의 상태 레지스터를 읽어 안다. 보통 코드와 나눌 값은 우선순위와 큐의 규율로 오간다(spsc 링 버퍼). 그래서 E-ISR-PARAMS 다.

반례. 인터럽트 처리기의 효과 줄을 비운다

examples/ch30/mistake_isreffect.low

module mistake_isreffect .
rem flags: --target cortex_m
rem expect: E-ISR-EFFECT

rem ✘ 할 일이 아직 없어 효과 줄을 비웠다
proc on_exti
  vector 6 .
  output void .
do
  return .
end

실행 결과

$ lowentc --check --target cortex_m mistake_isreffect.low
mistake_isreffect.low:6:0 E-ISR-EFFECT: an interrupt handler must declare `effects device` — it exists because the device asked for it

할 일이 아직 없어도 인터럽트 처리기는 장치 때문에 있는 자리다. effects device . 를 적어야 이 op 이 장치 쪽 코드라는 것이 머리에 남고, 등급(build tier)과 권한 검사가 그 사실을 따라간다. 적지 않으면 E-ISR-EFFECT 다.

반례. asm 의 기계 이름을 다른 도구의 철자로 적는다

examples/ch30/mistake_asmtarget.low

module mistake_asmtarget .
rem expect: E-ASM-TARGET-UNKNOWN

unsafe proc add_native
  input k cap machine .
  input a u64 .
  input b u64 .
  output u64 .
  effects unsafe .
  rem ✘ 기계 이름을 다른 도구의 철자(`aarch64`)로 적었다 --- 이 처리기의 이름은 `arm64` 다
  asm aarch64 .
    reg a .
    reg b .
    out reg r .
do
  text ASM
    add {r}, {a}, {b}
  ASM
end

실행 결과

$ lowentc --check mistake_asmtarget.low
mistake_asmtarget.low:4:0 E-ASM-TARGET-UNKNOWN: this `asm` clause names something that is not a build target. The names are a CLOSED set — the target table is the authority (x86_64 · arm64 · cortex_m · riscv64 · mips_be). A name outside it matches NO build, so the op would be silently dropped or silently mis-built; both are what this RFC exists to prevent

GCC 나 LLVM 은 aarch64 라고 부르지만 이 처리기의 대상 이름은 arm64 다. 이름은 닫힌 목록(x86_64·arm64·cortex_m·riscv64·mips_be) 이고, 목록 밖의 이름은 어떤 빌드와도 맞지 않으므로 그 op 은 영영 지어지지 않는다. 그래서 조용히 건너뛰지 않고 E-ASM-TARGET-UNKNOWN 으로 멈춘다.

반례. 레지스터를 보통 칸처럼 읽고 쓴다

examples/ch30/mistake_plainfield.low

module mistake_plainfield .
rem expect: E-MMIO-PLAIN

struct gpio do
  mmio 0x40020000 .
  moder u32 rw .
  idr u32 ro .
  bsrr u32 wo .
end

unsafe proc drive input dev cap mmio . input regs mut slice u8 . output u32 . effects device unsafe .
do
  var g gpio be view gpio regs .
  rem ✘ 레지스터를 보통 칸처럼 쓰고 읽는다 --- 합치거나 지워도 되는 접근으로 읽힌다
  set (field g moder) 2 .
  return field g idr .
end

실행 결과

$ lowentc --check mistake_plainfield.low
mistake_plainfield.low:15:0 E-MMIO-PLAIN: this is a DEVICE register, and this reads/writes it like ordinary memory. An ordinary access is one the translator may merge or delete (two reads of the same field become one; a store nothing reads goes away) — for a device the access ITSELF is the work, so deleting it makes the hardware do the wrong thing. Write `read_volatile <block> <reg>` or `write_volatile <block> <reg> <value>`, which say exactly once, in the written order (RFC-0042 D1). The `ro`/`wo` permission is checked there too

set (field g moder) 2 와 field g idr 는 거절된다(E-MMIO-PLAIN). 보통 칸 접근은 처리기가 합치거나 지워도 되는 연산이기 때문이다 — 같은 레지스터를 두 번 읽는 코드가 한 번으로 줄거나, 아무도 읽지 않는 쓰기가 사라져도 보통 메모리에서는 답이 같다. 장치에서는 그 접근 자체가 일이므로 답이 달라진다. 장치 레지스터에는 read_volatile·write_volatile 을 쓴다 — 닿는 법(ro·wo)도 그 자리에서 함께 검사한다.

30.9 이 장의 문법 한눈에#

모양뜻왜 이렇게
struct gpio do mmio 0x40020000 . moder u32 rw . … end장치의 레지스터 지도새 낱말 없이 구조체에 절 하나
rw · ro · wo닿는 법 — 번역에서 강제어기면 E-MMIO-PERM
read_volatile g idr · write_volatile g moder 2합치거나 지우지 않는 접근읽는 행위 자체가 일이다
input dev cap mmio . + effects device장치 권한과 효과권한 없는 하드웨어 접근이 없다
var g gpio be view gpio regs .바이트 위에 지도를 얹는다장치를 값으로 받으면 E-MMIO-BYVALUE
proc on_exti vector 6 . priority 2 . output void . effects device .인터럽트 처리기부르면 E-ISR-CALLED · 인자는 E-ISR-PARAMS
build tier t1 .이 기계가 감당하는 효과의 등급실을 수 없는 것을 번역에서 막는다
asm x86_64 . reg a . out reg r . clobber flags . options pure .기계 명령으로 쓴 몸의 머리unsafe·cap machine·효과 줄·기계 이름으로 가둔다
text ASM … {a} … ASM템플릿 — 피연산자와 맞대어 검사E-ASM-UNBOUND · E-ASM-UNUSED · E-ASM-OPTLIE

표 30.4 — 하드웨어의 문법 — 모양 · 뜻 · 왜 이렇게 생겼나

복습 정리

mmio <주소> 를 붙인 구조체가 장치의 지도이고, read_volatile·write_volatile 이 합치거나 지우지 않는 접근이다. cap mmio 와 device 효과가 필요하며, ro·wo 표지는 번역에서 강제되고 장치는 값으로 받을 수 없다. vector 절의 op 은 인터럽트 처리기로, 부를 수도 인자를 받을 수도 없다. build tier 는 기계가 감당하는 효과를 정한다. asm 절은 기계 명령을 unsafe· cap machine·효과 줄·기계 이름으로 가두고, 피연산자와 템플릿을 양쪽으로 맞댄다.